diff --git a/.changeset/afraid-fans-cross.md b/.changeset/afraid-fans-cross.md new file mode 100644 index 0000000000..1901d8ce0f --- /dev/null +++ b/.changeset/afraid-fans-cross.md @@ -0,0 +1,8 @@ +--- +'@backstage/create-app': patch +'@backstage/cli': patch +--- + +Add `config.d.ts` files to the list of included file in `tsconfig.json`. + +This allows ESLint to detect issues or deprecations in those files. diff --git a/.changeset/angry-dolphins-camp.md b/.changeset/angry-dolphins-camp.md deleted file mode 100644 index 3c598f4a66..0000000000 --- a/.changeset/angry-dolphins-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Add access restrictions to the JWKS external access method config schema diff --git a/.changeset/angry-hotels-warn.md b/.changeset/angry-hotels-warn.md new file mode 100644 index 0000000000..07caaba574 --- /dev/null +++ b/.changeset/angry-hotels-warn.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': minor +'@backstage/backend-common': minor +--- + +**BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. diff --git a/.changeset/big-eagles-grab.md b/.changeset/big-eagles-grab.md deleted file mode 100644 index 9ece8f64df..0000000000 --- a/.changeset/big-eagles-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for github:environment:create action and improve its test cases diff --git a/.changeset/shaggy-mugs-return.md b/.changeset/big-spies-stare.md similarity index 50% rename from .changeset/shaggy-mugs-return.md rename to .changeset/big-spies-stare.md index 6af5d1d18f..bab1643284 100644 --- a/.changeset/shaggy-mugs-return.md +++ b/.changeset/big-spies-stare.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs-backend': patch --- -Update configuration schema to match actual behavior +Dedicated token for techdocs cache sync diff --git a/.changeset/blue-forks-cry.md b/.changeset/blue-forks-cry.md new file mode 100644 index 0000000000..1bcc1d0b59 --- /dev/null +++ b/.changeset/blue-forks-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +--- + +Fix a bug where the signer was checked from the payload instead of the header diff --git a/.changeset/blue-pumas-cheer.md b/.changeset/blue-pumas-cheer.md deleted file mode 100644 index f6cbf180ff..0000000000 --- a/.changeset/blue-pumas-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added the Kubernetes plugin to `create-app` diff --git a/.changeset/breezy-cats-kiss.md b/.changeset/breezy-cats-kiss.md deleted file mode 100644 index 900016c1e8..0000000000 --- a/.changeset/breezy-cats-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Fixing issue with extension blueprints `inputs` merging. diff --git a/.changeset/breezy-flowers-dance.md b/.changeset/breezy-flowers-dance.md new file mode 100644 index 0000000000..faa9fd0697 --- /dev/null +++ b/.changeset/breezy-flowers-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Deprecate the `featureDiscoveryServiceFactory` in favor of using `@backstage/backend-defaults#discoveryFeatureLoader` instead. diff --git a/.changeset/breezy-jeans-tie.md b/.changeset/breezy-jeans-tie.md deleted file mode 100644 index 4601389a4c..0000000000 --- a/.changeset/breezy-jeans-tie.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch -'@backstage/plugin-auth-backend-module-atlassian-provider': patch -'@backstage/plugin-auth-backend-module-bitbucket-provider': patch -'@backstage/plugin-auth-backend-module-microsoft-provider': patch -'@backstage/plugin-auth-backend-module-onelogin-provider': patch -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch -'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch -'@backstage/plugin-auth-backend-module-github-provider': patch -'@backstage/plugin-auth-backend-module-gitlab-provider': patch -'@backstage/plugin-auth-backend-module-google-provider': patch -'@backstage/plugin-auth-backend-module-oauth2-provider': patch -'@backstage/plugin-auth-backend-module-oidc-provider': patch -'@backstage/plugin-auth-backend-module-okta-provider': patch ---- - -Add `signIn` to authentication provider configuration schema diff --git a/.changeset/breezy-rings-fly.md b/.changeset/breezy-rings-fly.md deleted file mode 100644 index c3cc1c143d..0000000000 --- a/.changeset/breezy-rings-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes diff --git a/.changeset/bright-donkeys-buy.md b/.changeset/bright-donkeys-buy.md deleted file mode 100644 index ded16ccc2c..0000000000 --- a/.changeset/bright-donkeys-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. diff --git a/.changeset/bright-trainers-brake.md b/.changeset/bright-trainers-brake.md deleted file mode 100644 index 2299d83a32..0000000000 --- a/.changeset/bright-trainers-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add frontend-dynamic-container role to eslint config factory 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-brooms-fail.md b/.changeset/calm-brooms-fail.md new file mode 100644 index 0000000000..a87b5ad38c --- /dev/null +++ b/.changeset/calm-brooms-fail.md @@ -0,0 +1,38 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using the `dynamicPluginsFeatureDiscoveryLoader` to discover dynamic features in a new backend system. + +See usage examples below: + +Example using the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; +//... + +const backend = createBackend(); +backend.add(dynamicPluginsFeatureDiscoveryLoader); +//... +backend.start(); +``` + +Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; +import { myCustomModuleLoader } from './myCustomModuleLoader'; +//... + +const backend = createBackend(); +backend.add( + dynamicPluginsFeatureDiscoveryLoader({ + moduleLoader: myCustomModuleLoader, + }), +); +//... +backend.start(); +``` diff --git a/.changeset/calm-crabs-drop.md b/.changeset/calm-crabs-drop.md deleted file mode 100644 index 09bed7c091..0000000000 --- a/.changeset/calm-crabs-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -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. diff --git a/.changeset/chilled-cameras-change.md b/.changeset/chilled-cameras-change.md new file mode 100644 index 0000000000..8491761594 --- /dev/null +++ b/.changeset/chilled-cameras-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Internal update to use the new cache manager diff --git a/.changeset/chilly-birds-shout.md b/.changeset/chilly-birds-shout.md new file mode 100644 index 0000000000..dfa99b6f5d --- /dev/null +++ b/.changeset/chilly-birds-shout.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-common': minor +--- + +**BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + +- The deprecated `dropDatabase` function has now been removed, without replacement. diff --git a/.changeset/chilly-days-peel.md b/.changeset/chilly-days-peel.md deleted file mode 100644 index d82344d364..0000000000 --- a/.changeset/chilly-days-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. diff --git a/.changeset/chilly-trains-sleep.md b/.changeset/chilly-trains-sleep.md deleted file mode 100644 index cde7600c8b..0000000000 --- a/.changeset/chilly-trains-sleep.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-events-backend-module-aws-sqs': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/backend-common': patch ---- - -Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. diff --git a/.changeset/clever-pans-brake.md b/.changeset/clever-pans-brake.md deleted file mode 100644 index 1f49c7a842..0000000000 --- a/.changeset/clever-pans-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Added new APIs for testing extensions 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-actors-sin.md b/.changeset/cool-actors-sin.md new file mode 100644 index 0000000000..4d5b2589c5 --- /dev/null +++ b/.changeset/cool-actors-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +--- + +Throw correct error when email is missing from the claims diff --git a/.changeset/cool-insects-remember.md b/.changeset/cool-insects-remember.md deleted file mode 100644 index ab33015377..0000000000 --- a/.changeset/cool-insects-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-notifications': patch ---- - -Add examples for notification:send scaffolder action & improve related tests diff --git a/.changeset/cool-melons-check.md b/.changeset/cool-melons-check.md new file mode 100644 index 0000000000..462a023dca --- /dev/null +++ b/.changeset/cool-melons-check.md @@ -0,0 +1,21 @@ +--- +'@backstage/frontend-test-utils': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +'@backstage/plugin-org': patch +--- + +Updated exports to use the new type parameters for extensions and extension blueprints. 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/cool-schools-vanish.md b/.changeset/cool-schools-vanish.md deleted file mode 100644 index 9b2ded5248..0000000000 --- a/.changeset/cool-schools-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fixing loading of additional config files with new `ConfigSources` diff --git a/.changeset/create-app-1722413762.md b/.changeset/create-app-1722413762.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1722413762.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/cuddly-zebras-crash.md b/.changeset/cuddly-zebras-crash.md deleted file mode 100644 index 473ac4053f..0000000000 --- a/.changeset/cuddly-zebras-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Use ES2022 in CLI bundler 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-pillows-joke.md b/.changeset/curvy-pillows-joke.md deleted file mode 100644 index 25966a51c0..0000000000 --- a/.changeset/curvy-pillows-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. diff --git a/.changeset/cyan-shrimps-push.md b/.changeset/cyan-shrimps-push.md deleted file mode 100644 index cb326b4da3..0000000000 --- a/.changeset/cyan-shrimps-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. diff --git a/.changeset/dry-beers-shake.md b/.changeset/dry-beers-shake.md new file mode 100644 index 0000000000..59ff5ead54 --- /dev/null +++ b/.changeset/dry-beers-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`. diff --git a/.changeset/dry-glasses-push.md b/.changeset/dry-glasses-push.md new file mode 100644 index 0000000000..31a04db24a --- /dev/null +++ b/.changeset/dry-glasses-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': patch +--- + +Extend the "unable to resolve user identity" message 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/dry-squids-tap.md b/.changeset/dry-squids-tap.md deleted file mode 100644 index 0a95332eed..0000000000 --- a/.changeset/dry-squids-tap.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` - -This allows the creation of extension instances with the following pattern: - -```tsx -// create the extension blueprint which is used to create instances -const EntityCardBlueprint = createExtensionBlueprint({ - kind: 'entity-card', - attachTo: { id: 'test', input: 'default' }, - output: [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', - }, -}); -``` diff --git a/.changeset/dull-ghosts-double.md b/.changeset/dull-ghosts-double.md deleted file mode 100644 index 0bc5a09791..0000000000 --- a/.changeset/dull-ghosts-double.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-home': patch ---- - -Updated alpha definitions of extension data references. diff --git a/.changeset/early-trees-dance.md b/.changeset/early-trees-dance.md deleted file mode 100644 index 55b2c08f89..0000000000 --- a/.changeset/early-trees-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. diff --git a/.changeset/eighty-emus-leave.md b/.changeset/eighty-emus-leave.md deleted file mode 100644 index 8e02c7cf94..0000000000 --- a/.changeset/eighty-emus-leave.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-techdocs-node': patch -'@backstage/plugin-techdocs': patch ---- - -Use annotation constants from new techdocs-common package. diff --git a/.changeset/eighty-jokes-deny.md b/.changeset/eighty-jokes-deny.md deleted file mode 100644 index 58944252f4..0000000000 --- a/.changeset/eighty-jokes-deny.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-scaffolder-node': patch ---- - -Add support for status filtering in scaffolder tasks endpoint diff --git a/.changeset/eighty-mirrors-flow.md b/.changeset/eighty-mirrors-flow.md deleted file mode 100644 index 57e978a78c..0000000000 --- a/.changeset/eighty-mirrors-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component diff --git a/.changeset/eighty-tables-hope.md b/.changeset/eighty-tables-hope.md new file mode 100644 index 0000000000..203cc8bb65 --- /dev/null +++ b/.changeset/eighty-tables-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Internal update to add support for passing an `ApiRegistry` when creating the node tree diff --git a/.changeset/empty-coats-sparkle.md b/.changeset/empty-coats-sparkle.md new file mode 100644 index 0000000000..7e3b791c6b --- /dev/null +++ b/.changeset/empty-coats-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +Removed support for testing "v1" extensions, where outputs are defined as an object rather than an array. diff --git a/.changeset/fair-hairs-mix.md b/.changeset/fair-hairs-mix.md deleted file mode 100644 index 6c06c7bf72..0000000000 --- a/.changeset/fair-hairs-mix.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-permission-common': patch ---- - -Add the MetadataResponse type from @backstage/plugin-permission-node, since this -type might be used in frontend code. diff --git a/.changeset/fair-pumas-hang.md b/.changeset/fair-pumas-hang.md deleted file mode 100644 index 156779b76a..0000000000 --- a/.changeset/fair-pumas-hang.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-search-backend-module-stack-overflow-collator': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch -'@backstage/plugin-catalog-backend-module-bitbucket-server': patch -'@backstage/plugin-auth-backend-module-microsoft-provider': patch -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket': patch -'@backstage/plugin-scaffolder-backend-module-gerrit': patch -'@backstage/plugin-scaffolder-backend-module-sentry': patch -'@backstage/plugin-catalog-backend-module-puppetdb': patch -'@backstage/plugin-scaffolder-backend-module-gitea': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-search-backend-module-techdocs': patch -'@backstage/plugin-catalog-backend-module-gerrit': patch -'@backstage/plugin-catalog-backend-module-github': patch -'@backstage/plugin-catalog-backend-module-gitlab': patch -'@backstage/plugin-search-backend-module-explore': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-notifications-node': patch -'@backstage/plugin-permission-backend': patch -'@backstage/backend-defaults': patch -'@backstage/backend-app-api': patch -'@backstage/plugin-devtools-backend': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/backend-common': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-signals-backend': patch -'@backstage/config-loader': patch -'@backstage/plugin-proxy-backend': patch -'@backstage/plugin-auth-backend': patch -'@backstage/create-app': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-auth-node': patch -'@backstage/cli': patch ---- - -Make sure node-fetch is version 2.7.0 or greater diff --git a/.changeset/famous-badgers-drop.md b/.changeset/famous-badgers-drop.md new file mode 100644 index 0000000000..af519cf964 --- /dev/null +++ b/.changeset/famous-badgers-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Added missing service mock for `mockServices.rootConfig.mock`, and fixed the definition of `mockServices.rootHttpRouter.factory` to not have a duplicate callback. diff --git a/.changeset/fast-bulldogs-relax.md b/.changeset/fast-bulldogs-relax.md deleted file mode 100644 index 65895345e9..0000000000 --- a/.changeset/fast-bulldogs-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch ---- - -Added autocompletion support for resource `branches` diff --git a/.changeset/few-wasps-hug.md b/.changeset/few-wasps-hug.md deleted file mode 100644 index 66cf34fc0b..0000000000 --- a/.changeset/few-wasps-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Correct `EntityDisplayName`'s icon alignment with the text. diff --git a/.changeset/five-tigers-share.md b/.changeset/five-tigers-share.md new file mode 100644 index 0000000000..507614c8b8 --- /dev/null +++ b/.changeset/five-tigers-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Found the issue during testing the clean up of the workspace for the database implementation. diff --git a/.changeset/flat-kangaroos-push.md b/.changeset/flat-kangaroos-push.md new file mode 100644 index 0000000000..b046beeeb1 --- /dev/null +++ b/.changeset/flat-kangaroos-push.md @@ -0,0 +1,31 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Updated the type parameters for `ExtensionDefinition` and `ExtensionBlueprint` to only have a single object parameter. The base type parameter is exported as `ExtensionDefinitionParameters` and `ExtensionBlueprintParameters` respectively. This is shipped as an immediate breaking change as we expect usage of these types to be rare, and it does not affect the runtime behavior of the API. + +This is a breaking change as it changes the type parameters. Existing usage can generally be updated as follows: + +- `ExtensionDefinition` -> `ExtensionDefinition` +- `ExtensionDefinition` -> `ExtensionDefinition` +- `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig }>` +- `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig, configInput: TConfigInput }>` + +If you need to infer the parameter you can use `ExtensionDefinitionParameters`, for example: + +```ts +import { + ExtensionDefinition, + ExtensionDefinitionParameters, +} from '@backstage/frontend-plugin-api'; + +function myUtility( + ext: ExtensionDefinition, +): T['config'] { + // ... +} +``` + +The same patterns apply to `ExtensionBlueprint`. + +This change is made to improve the readability of API references and ability to evolve the type parameters in the future. diff --git a/.changeset/flat-papayas-push.md b/.changeset/flat-papayas-push.md deleted file mode 100644 index f9e20613fd..0000000000 --- a/.changeset/flat-papayas-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-azure': patch ---- - -Added examples for publish:azure action and updated its test cases diff --git a/.changeset/flat-plums-grow.md b/.changeset/flat-plums-grow.md deleted file mode 100644 index 7057216786..0000000000 --- a/.changeset/flat-plums-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` diff --git a/.changeset/forty-ties-agree.md b/.changeset/forty-ties-agree.md deleted file mode 100644 index caa45841a2..0000000000 --- a/.changeset/forty-ties-agree.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -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, - }, - }, -}); -``` diff --git a/.changeset/forty-ties-disagree.md b/.changeset/forty-ties-disagree.md deleted file mode 100644 index 214097b35d..0000000000 --- a/.changeset/forty-ties-disagree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': patch ---- - -Support icon overriding with the new `IconBundleBlueprint` API. diff --git a/.changeset/forty-toes-float.md b/.changeset/forty-toes-float.md new file mode 100644 index 0000000000..b6ca5139ea --- /dev/null +++ b/.changeset/forty-toes-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +**BREAKING**: The deprecated `.render()` method has been removed from the extension tester. diff --git a/.changeset/four-parents-buy.md b/.changeset/four-parents-buy.md new file mode 100644 index 0000000000..ac216c3c08 --- /dev/null +++ b/.changeset/four-parents-buy.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: The `createSpecializedApp` function now creates a bare-bones app without any of the default app structure or APIs. To re-introduce this functionality if you need to use `createSpecializedApp` you can install the `app` plugin from `@backstage/plugin-app`. + +In addition, the `createApp` and `CreateAppFeatureLoader` exports are now deprecated as they are being moved to `@backstage/frontend-defaults`, which should be used instead. diff --git a/.changeset/four-ties-raise.md b/.changeset/four-ties-raise.md new file mode 100644 index 0000000000..163443afff --- /dev/null +++ b/.changeset/four-ties-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +Add `ui:backstage.review.name` option for custom item names on scaffolder review page, and also add support for rendering the `title` property instead of the key name. diff --git a/.changeset/fresh-apes-dress.md b/.changeset/fresh-apes-dress.md new file mode 100644 index 0000000000..6e20e53bcd --- /dev/null +++ b/.changeset/fresh-apes-dress.md @@ -0,0 +1,8 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/frontend-app-api': patch +'@backstage/plugin-app': minor +--- + +Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. diff --git a/.changeset/fresh-bears-prove.md b/.changeset/fresh-bears-prove.md new file mode 100644 index 0000000000..a4098a4b18 --- /dev/null +++ b/.changeset/fresh-bears-prove.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +'@backstage/create-app': patch +--- + +Update templates to not refer to backend-common diff --git a/.changeset/fresh-pumas-clean.md b/.changeset/fresh-pumas-clean.md new file mode 100644 index 0000000000..2140f62651 --- /dev/null +++ b/.changeset/fresh-pumas-clean.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-app-backend': patch +'@backstage/plugin-app-node': patch +--- + +Fixing dependency metadata with the new `@backstage/plugin-app` package diff --git a/.changeset/friendly-cherries-applaud.md b/.changeset/friendly-cherries-applaud.md deleted file mode 100644 index c0160858f2..0000000000 --- a/.changeset/friendly-cherries-applaud.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-permission-node': patch ---- - -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. diff --git a/.changeset/friendly-chicken-cry.md b/.changeset/friendly-chicken-cry.md deleted file mode 100644 index 3eadca8777..0000000000 --- a/.changeset/friendly-chicken-cry.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch -'@backstage/core-compat-api': patch -'@backstage/plugin-app-visualizer': patch ---- - -Updated usage of `useRouteRef`, which can now always return `undefined`. diff --git a/.changeset/friendly-days-march.md b/.changeset/friendly-days-march.md new file mode 100644 index 0000000000..a190c50a43 --- /dev/null +++ b/.changeset/friendly-days-march.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + +Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. diff --git a/.changeset/friendly-feet-refuse.md b/.changeset/friendly-feet-refuse.md deleted file mode 100644 index 677d21d48a..0000000000 --- a/.changeset/friendly-feet-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-rails': patch ---- - -Add examples for fetch:rails scaffolder action & improve related tests diff --git a/.changeset/friendly-months-march.md b/.changeset/friendly-months-march.md new file mode 100644 index 0000000000..0bb9cd7cf0 --- /dev/null +++ b/.changeset/friendly-months-march.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +--- + +Added support for new `FrontendPlugin` and `FrontendModule` types. diff --git a/.changeset/funny-bears-sort.md b/.changeset/funny-bears-sort.md deleted file mode 100644 index a3dad0f489..0000000000 --- a/.changeset/funny-bears-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the `process` polyfill to use `require.resolve` for greater compatability. diff --git a/.changeset/funny-houses-rest.md b/.changeset/funny-houses-rest.md new file mode 100644 index 0000000000..21efce8c19 --- /dev/null +++ b/.changeset/funny-houses-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +The `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. diff --git a/.changeset/funny-wolves-learn.md b/.changeset/funny-wolves-learn.md deleted file mode 100644 index 2010ccd68b..0000000000 --- a/.changeset/funny-wolves-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:issues:create examples diff --git a/.changeset/fuzzy-feet-exist.md b/.changeset/fuzzy-feet-exist.md new file mode 100644 index 0000000000..35ef1e58fd --- /dev/null +++ b/.changeset/fuzzy-feet-exist.md @@ -0,0 +1,34 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/plugin-app': patch +--- + +Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + +You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + +```ts +// Before +createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], +}); + +// After +createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], +}); +``` diff --git a/.changeset/fuzzy-mails-walk.md b/.changeset/fuzzy-mails-walk.md new file mode 100644 index 0000000000..b46fbd59c4 --- /dev/null +++ b/.changeset/fuzzy-mails-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Deprecate the `featureDiscoveryServiceRef` in favor of using the new `discoveryFeatureLoader` instead. diff --git a/.changeset/fuzzy-spies-share.md b/.changeset/fuzzy-spies-share.md new file mode 100644 index 0000000000..99108b81a0 --- /dev/null +++ b/.changeset/fuzzy-spies-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-signals-backend': patch +--- + +The `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. diff --git a/.changeset/gentle-dryers-smile.md b/.changeset/gentle-dryers-smile.md deleted file mode 100644 index b061461da1..0000000000 --- a/.changeset/gentle-dryers-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. diff --git a/.changeset/gentle-hats-act.md b/.changeset/gentle-hats-act.md new file mode 100644 index 0000000000..e78076c9c5 --- /dev/null +++ b/.changeset/gentle-hats-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Added a new `CreateAppOptions` type for the `createApp` options. diff --git a/.changeset/giant-jars-mix.md b/.changeset/giant-jars-mix.md new file mode 100644 index 0000000000..652e23a280 --- /dev/null +++ b/.changeset/giant-jars-mix.md @@ -0,0 +1,6 @@ +--- +'@backstage/create-app': patch +'@backstage/plugin-devtools': patch +--- + +Minor dockerfile syntax update diff --git a/.changeset/good-steaks-report.md b/.changeset/good-steaks-report.md deleted file mode 100644 index 5f9ce25470..0000000000 --- a/.changeset/good-steaks-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitea': patch ---- - -Added test cases for publish:gitea examples diff --git a/.changeset/gorgeous-scissors-wave.md b/.changeset/gorgeous-scissors-wave.md new file mode 100644 index 0000000000..1db406b940 --- /dev/null +++ b/.changeset/gorgeous-scissors-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Updated the error message for missing service dependencies to include the plugin and module IDs. diff --git a/.changeset/green-planets-reflect.md b/.changeset/green-planets-reflect.md deleted file mode 100644 index 513f0480a5..0000000000 --- a/.changeset/green-planets-reflect.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch ---- - -Added support for v2 extensions, which declare their inputs and outputs without using a data map. diff --git a/.changeset/green-worms-rescue.md b/.changeset/green-worms-rescue.md new file mode 100644 index 0000000000..992012ad7d --- /dev/null +++ b/.changeset/green-worms-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add `checks: 'read'` for default GitHub app permissions diff --git a/.changeset/grumpy-owls-suffer.md b/.changeset/grumpy-owls-suffer.md deleted file mode 100644 index 977055eb33..0000000000 --- a/.changeset/grumpy-owls-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Allow the `createGitlabProjectVariableAction` to use oauth tokens diff --git a/.changeset/healthy-fireants-jump.md b/.changeset/healthy-fireants-jump.md deleted file mode 100644 index 7a11015b80..0000000000 --- a/.changeset/healthy-fireants-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -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. diff --git a/.changeset/healthy-moons-drum.md b/.changeset/healthy-moons-drum.md new file mode 100644 index 0000000000..0f61d0b141 --- /dev/null +++ b/.changeset/healthy-moons-drum.md @@ -0,0 +1,9 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Removed the following deprecated exports + +- `BackendPluginConfig` use `CreateBackendPluginOptions` +- `BackendModuleConfig` use `CreateBackendModuleOptions` +- `ExtensionPointConfig` use `CreateExtensionPointOptions` diff --git a/.changeset/healthy-timers-divide.md b/.changeset/healthy-timers-divide.md deleted file mode 100644 index 0b9d700723..0000000000 --- a/.changeset/healthy-timers-divide.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-search-common': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-search': patch ---- - -Fix package metadata diff --git a/.changeset/heavy-numbers-love.md b/.changeset/heavy-numbers-love.md deleted file mode 100644 index c18d144636..0000000000 --- a/.changeset/heavy-numbers-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. diff --git a/.changeset/heavy-suits-judge.md b/.changeset/heavy-suits-judge.md new file mode 100644 index 0000000000..d5c00184c7 --- /dev/null +++ b/.changeset/heavy-suits-judge.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings-backend': patch +--- + +In preparation to stop supporting to the legacy backend system, the `createRouter` function is now deprecated and we strongly recommend you [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) your backend to the new system. diff --git a/.changeset/hip-fishes-guess.md b/.changeset/hip-fishes-guess.md deleted file mode 100644 index 909448c673..0000000000 --- a/.changeset/hip-fishes-guess.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch -'@backstage/backend-defaults': patch ---- - -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`. diff --git a/.changeset/hip-hairs-exist.md b/.changeset/hip-hairs-exist.md deleted file mode 100644 index d8cb939dfb..0000000000 --- a/.changeset/hip-hairs-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-node': patch ---- - -Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint diff --git a/.changeset/tricky-rules-destroy.md b/.changeset/hip-pandas-think.md similarity index 50% rename from .changeset/tricky-rules-destroy.md rename to .changeset/hip-pandas-think.md index 196702f8c4..a43349db63 100644 --- a/.changeset/tricky-rules-destroy.md +++ b/.changeset/hip-pandas-think.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs-node': patch --- -Fix TechDocs Edit URL for nested docs +Fix typo and unify TechDocs casing in doc strings diff --git a/.changeset/itchy-experts-tie.md b/.changeset/itchy-experts-tie.md deleted file mode 100644 index c5bf99ecaf..0000000000 --- a/.changeset/itchy-experts-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for github:repo:create action and improved test cases diff --git a/.changeset/khaki-lamps-peel.md b/.changeset/khaki-lamps-peel.md deleted file mode 100644 index 82a2cbc9de..0000000000 --- a/.changeset/khaki-lamps-peel.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -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'); - } - }, -}); -``` diff --git a/.changeset/kind-moose-learn.md b/.changeset/kind-moose-learn.md new file mode 100644 index 0000000000..810064b1a1 --- /dev/null +++ b/.changeset/kind-moose-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +**BREAKING**: The deprecated `identityServiceFactory` and `tokenManagerServiceFactory` have been removed. diff --git a/.changeset/kind-walls-speak.md b/.changeset/kind-walls-speak.md new file mode 100644 index 0000000000..441e247dea --- /dev/null +++ b/.changeset/kind-walls-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': minor +--- + +**BREAKING**: The backwards compatibility with plugins using legacy auth through the token manager service has been removed. This means that instead of falling back to using the old token manager, requests towards plugins that don't support the new auth system will simply fail. Please make sure that all plugins in your deployment are hosted within a backend instance from the new backend system. diff --git a/.changeset/large-poets-check.md b/.changeset/large-poets-check.md new file mode 100644 index 0000000000..393d26ac9b --- /dev/null +++ b/.changeset/large-poets-check.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-auth-backend': patch +--- + +Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. diff --git a/.changeset/late-games-protect.md b/.changeset/late-games-protect.md deleted file mode 100644 index 584780f836..0000000000 --- a/.changeset/late-games-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': minor ---- - -Add scaffolder option to display object items in separate rows on review page diff --git a/.changeset/light-pianos-exercise.md b/.changeset/light-pianos-exercise.md deleted file mode 100644 index 4bf161eb06..0000000000 --- a/.changeset/light-pianos-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws diff --git a/.changeset/little-bulldogs-guess.md b/.changeset/little-bulldogs-guess.md deleted file mode 100644 index 3e646a033f..0000000000 --- a/.changeset/little-bulldogs-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -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. diff --git a/.changeset/little-suns-fly.md b/.changeset/little-suns-fly.md deleted file mode 100644 index 887df4be6d..0000000000 --- a/.changeset/little-suns-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Added option to ingest groups based on their group membership in Azure Entra ID diff --git a/.changeset/loud-brooms-pull.md b/.changeset/loud-brooms-pull.md new file mode 100644 index 0000000000..8ed3e22c5b --- /dev/null +++ b/.changeset/loud-brooms-pull.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-client': patch +'@backstage/plugin-catalog-backend': patch +--- + +Moved `getEntities` ordering to utilize database instead of having it inside catalog client + +Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. diff --git a/.changeset/lucky-sheep-cover.md b/.changeset/lucky-sheep-cover.md new file mode 100644 index 0000000000..9b9420a73a --- /dev/null +++ b/.changeset/lucky-sheep-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +The `legacyPlugin` and `makeLegacyPlugin` helpers now provide their own shim implementation of the identity and token manager services, as these services are being removed from the new backend system. diff --git a/.changeset/mean-apricots-perform.md b/.changeset/mean-apricots-perform.md deleted file mode 100644 index c93514cb7f..0000000000 --- a/.changeset/mean-apricots-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. diff --git a/.changeset/metal-garlics-fetch.md b/.changeset/metal-garlics-fetch.md new file mode 100644 index 0000000000..076b5f34a5 --- /dev/null +++ b/.changeset/metal-garlics-fetch.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +--- + +Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension. diff --git a/.changeset/metal-planes-nail.md b/.changeset/metal-planes-nail.md deleted file mode 100644 index 2cfa12a435..0000000000 --- a/.changeset/metal-planes-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-tasks': patch ---- - -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`. diff --git a/.changeset/metal-rice-call.md b/.changeset/metal-rice-call.md deleted file mode 100644 index 46e30dd26f..0000000000 --- a/.changeset/metal-rice-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Introduces the HasSubdomainsCard component that displays the subdomains of a given domain diff --git a/.changeset/metal-rivers-grin.md b/.changeset/metal-rivers-grin.md new file mode 100644 index 0000000000..b70750c98d --- /dev/null +++ b/.changeset/metal-rivers-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +There is a new `mockErrorHandler` utility to help in mocking the error middleware in tests. diff --git a/.changeset/mighty-apricots-taste.md b/.changeset/mighty-apricots-taste.md deleted file mode 100644 index 80b70020cb..0000000000 --- a/.changeset/mighty-apricots-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. diff --git a/.changeset/mighty-cheetahs-call.md b/.changeset/mighty-cheetahs-call.md new file mode 100644 index 0000000000..f5d17d8b20 --- /dev/null +++ b/.changeset/mighty-cheetahs-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Move down the discovery config to be in the root diff --git a/.changeset/mighty-days-kiss.md b/.changeset/mighty-days-kiss.md new file mode 100644 index 0000000000..f78e602e25 --- /dev/null +++ b/.changeset/mighty-days-kiss.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/backend-app-api': patch +--- + +`auth.externalAccess` should be optional in the config schema diff --git a/.changeset/mighty-dolls-retire.md b/.changeset/mighty-dolls-retire.md deleted file mode 100644 index b31c443a38..0000000000 --- a/.changeset/mighty-dolls-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. diff --git a/.changeset/mighty-geckos-kiss.md b/.changeset/mighty-geckos-kiss.md deleted file mode 100644 index 0761d8b257..0000000000 --- a/.changeset/mighty-geckos-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-node': patch ---- - -Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. diff --git a/.changeset/modern-parrots-protect.md b/.changeset/modern-parrots-protect.md deleted file mode 100644 index bb4df2c248..0000000000 --- a/.changeset/modern-parrots-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -support `ajv-errors` for scaffolder validation to allow for customizing the error messages diff --git a/.changeset/modern-poems-mate.md b/.changeset/modern-poems-mate.md deleted file mode 100644 index 06926daac9..0000000000 --- a/.changeset/modern-poems-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -Allow using notifications without users in the catalog diff --git a/.changeset/nasty-tigers-knock.md b/.changeset/nasty-tigers-knock.md new file mode 100644 index 0000000000..c48d725779 --- /dev/null +++ b/.changeset/nasty-tigers-knock.md @@ -0,0 +1,37 @@ +--- +'@backstage/backend-common': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-events-backend-module-azure': patch +'@backstage/plugin-events-backend-module-bitbucket-cloud': patch +'@backstage/plugin-events-backend-module-gerrit': patch +'@backstage/plugin-events-backend-module-github': patch +'@backstage/plugin-events-backend-module-gitlab': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-user-settings-backend': patch +--- + +Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. diff --git a/.changeset/neat-bears-divide.md b/.changeset/neat-bears-divide.md deleted file mode 100644 index a94b17f74d..0000000000 --- a/.changeset/neat-bears-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -The `env` option of `ConfigSources.default` now correctly allows undefined members. diff --git a/.changeset/neat-gifts-join.md b/.changeset/neat-gifts-join.md deleted file mode 100644 index cd140c4692..0000000000 --- a/.changeset/neat-gifts-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-devtools-backend': patch ---- - -Removed unused code for lockfile analysis. diff --git a/.changeset/neat-socks-cheer.md b/.changeset/neat-socks-cheer.md deleted file mode 100644 index ffc76bec3f..0000000000 --- a/.changeset/neat-socks-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-node': minor ---- - -**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. diff --git a/.changeset/new-poets-unite.md b/.changeset/new-poets-unite.md new file mode 100644 index 0000000000..83172ffd66 --- /dev/null +++ b/.changeset/new-poets-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Make k8s entity content appear on components & resources only by default in new FE system diff --git a/.changeset/new-scissors-try.md b/.changeset/new-scissors-try.md deleted file mode 100644 index a9e9a45ce7..0000000000 --- a/.changeset/new-scissors-try.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-microsoft-provider': patch ---- - -Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. diff --git a/.changeset/nice-peas-retire.md b/.changeset/nice-peas-retire.md deleted file mode 100644 index 7b7b13706d..0000000000 --- a/.changeset/nice-peas-retire.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/integration': minor -'@backstage/backend-defaults': patch ---- - -Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. diff --git a/.changeset/nine-cherries-decide.md b/.changeset/nine-cherries-decide.md deleted file mode 100644 index b20c24aa85..0000000000 --- a/.changeset/nine-cherries-decide.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -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). diff --git a/.changeset/nine-glasses-nail.md b/.changeset/nine-glasses-nail.md deleted file mode 100644 index 138acce3dd..0000000000 --- a/.changeset/nine-glasses-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-pg': patch ---- - -Removing `@backstage/backend-app-api` dependency diff --git a/.changeset/nine-seahorses-relate.md b/.changeset/nine-seahorses-relate.md deleted file mode 100644 index 9a5bb43823..0000000000 --- a/.changeset/nine-seahorses-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-common': patch ---- - -Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. diff --git a/.changeset/ninety-icons-smile.md b/.changeset/ninety-icons-smile.md deleted file mode 100644 index f137879706..0000000000 --- a/.changeset/ninety-icons-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Remove usage of deprecated functionality from @backstage/config-loader diff --git a/.changeset/ninety-mayflies-raise.md b/.changeset/ninety-mayflies-raise.md new file mode 100644 index 0000000000..62042c2615 --- /dev/null +++ b/.changeset/ninety-mayflies-raise.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-pg': patch +--- + +Internal refactor to use `LoggerService` and `DatabaseService` instead of the legacy `Logger` and `PluginDatabaseManager` types. diff --git a/.changeset/odd-books-share.md b/.changeset/odd-books-share.md deleted file mode 100644 index 858a2564ad..0000000000 --- a/.changeset/odd-books-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. diff --git a/.changeset/odd-goats-kiss.md b/.changeset/odd-goats-kiss.md new file mode 100644 index 0000000000..0f199c1a1e --- /dev/null +++ b/.changeset/odd-goats-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` diff --git a/.changeset/old-tools-smell.md b/.changeset/old-tools-smell.md deleted file mode 100644 index 4dbd65e779..0000000000 --- a/.changeset/old-tools-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -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. diff --git a/.changeset/olive-books-sort.md b/.changeset/olive-books-sort.md deleted file mode 100644 index d7bfd925ce..0000000000 --- a/.changeset/olive-books-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-node': patch ---- - -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. diff --git a/.changeset/silver-pillows-begin.md b/.changeset/olive-phones-sniff.md similarity index 57% rename from .changeset/silver-pillows-begin.md rename to .changeset/olive-phones-sniff.md index 8fe1ccc230..d70953435c 100644 --- a/.changeset/silver-pillows-begin.md +++ b/.changeset/olive-phones-sniff.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Internal type refactor. +Add `pg-format` as a dependency diff --git a/.changeset/orange-gifts-protect.md b/.changeset/orange-gifts-protect.md deleted file mode 100644 index 1de9e2a6ba..0000000000 --- a/.changeset/orange-gifts-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch ---- - -Added examples for publish:bitbucketServer action and improve its test cases diff --git a/.changeset/pink-gorillas-brake.md b/.changeset/pink-gorillas-brake.md deleted file mode 100644 index 188884f13c..0000000000 --- a/.changeset/pink-gorillas-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fix extra divider displayed on user list picker component diff --git a/.changeset/plenty-dragons-know.md b/.changeset/plenty-dragons-know.md new file mode 100644 index 0000000000..2aa8458011 --- /dev/null +++ b/.changeset/plenty-dragons-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Added `createPublicSignInApp`, used to creating apps for the public entry point. diff --git a/.changeset/plenty-tools-exist.md b/.changeset/plenty-tools-exist.md deleted file mode 100644 index 0ce22a74f2..0000000000 --- a/.changeset/plenty-tools-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:issue:edit examples diff --git a/.changeset/popular-cooks-camp.md b/.changeset/popular-cooks-camp.md new file mode 100644 index 0000000000..4049825efd --- /dev/null +++ b/.changeset/popular-cooks-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +**BREAKING**: Removed service mocks for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. diff --git a/.changeset/popular-panthers-hear.md b/.changeset/popular-panthers-hear.md deleted file mode 100644 index c31586ea82..0000000000 --- a/.changeset/popular-panthers-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -TechDocs redirect feature now includes a notification to the user before they are redirected. diff --git a/.changeset/pre.json b/.changeset/pre.json index 4699170212..0266c977a1 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,311 +2,309 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.99", - "@backstage/app-defaults": "1.5.8", - "example-app-next": "0.0.13", - "app-next-example-plugin": "0.0.13", - "example-backend": "0.0.28", - "@backstage/backend-app-api": "0.8.0", - "@backstage/backend-common": "0.23.3", - "@backstage/backend-defaults": "0.4.0", - "@backstage/backend-dev-utils": "0.1.4", - "@backstage/backend-dynamic-feature-service": "0.2.15", - "example-backend-legacy": "0.2.100", - "@backstage/backend-openapi-utils": "0.1.15", - "@backstage/backend-plugin-api": "0.7.0", - "@backstage/backend-tasks": "0.5.27", - "@backstage/backend-test-utils": "0.4.4", - "@backstage/catalog-client": "1.6.5", - "@backstage/catalog-model": "1.5.0", - "@backstage/cli": "0.26.11", + "example-app": "0.2.100", + "@backstage/app-defaults": "1.5.10", + "example-app-next": "0.0.14", + "app-next-example-plugin": "0.0.14", + "example-backend": "0.0.29", + "@backstage/backend-app-api": "0.9.0", + "@backstage/backend-common": "0.24.0", + "@backstage/backend-defaults": "0.4.2", + "@backstage/backend-dev-utils": "0.1.5", + "@backstage/backend-dynamic-feature-service": "0.3.0", + "example-backend-legacy": "0.2.101", + "@backstage/backend-openapi-utils": "0.1.16", + "@backstage/backend-plugin-api": "0.8.0", + "@backstage/backend-test-utils": "0.5.0", + "@backstage/catalog-client": "1.6.6", + "@backstage/catalog-model": "1.6.0", + "@backstage/cli": "0.27.0", "@backstage/cli-common": "0.1.14", "@backstage/cli-node": "0.2.7", "@backstage/codemods": "0.1.49", "@backstage/config": "1.2.0", - "@backstage/config-loader": "1.8.1", - "@backstage/core-app-api": "1.14.0", - "@backstage/core-compat-api": "0.2.7", - "@backstage/core-components": "0.14.9", + "@backstage/config-loader": "1.9.0", + "@backstage/core-app-api": "1.14.2", + "@backstage/core-compat-api": "0.2.8", + "@backstage/core-components": "0.14.10", "@backstage/core-plugin-api": "1.9.3", - "@backstage/create-app": "0.5.17", - "@backstage/dev-utils": "1.0.35", - "e2e-test": "0.2.18", + "@backstage/create-app": "0.5.18", + "@backstage/dev-utils": "1.0.37", + "e2e-test": "0.2.19", "@backstage/e2e-test-utils": "0.1.1", "@backstage/errors": "1.2.4", "@backstage/eslint-plugin": "0.1.8", - "@backstage/frontend-app-api": "0.7.3", - "@backstage/frontend-plugin-api": "0.6.7", - "@backstage/frontend-test-utils": "0.1.10", - "@backstage/integration": "1.13.0", + "@backstage/frontend-app-api": "0.8.0", + "@backstage/frontend-plugin-api": "0.7.0", + "@backstage/frontend-test-utils": "0.1.12", + "@backstage/integration": "1.14.0", "@backstage/integration-aws-node": "0.1.12", - "@backstage/integration-react": "1.1.29", + "@backstage/integration-react": "1.1.30", "@backstage/release-manifests": "0.0.11", - "@backstage/repo-tools": "0.9.4", - "@techdocs/cli": "1.8.16", - "techdocs-cli-embedded-app": "0.2.98", - "@backstage/test-utils": "1.5.8", + "@backstage/repo-tools": "0.9.5", + "@techdocs/cli": "1.8.17", + "techdocs-cli-embedded-app": "0.2.99", + "@backstage/test-utils": "1.5.10", "@backstage/theme": "0.5.6", "@backstage/types": "1.1.1", "@backstage/version-bridge": "1.0.8", "yarn-plugin-backstage": "0.0.1", - "@backstage/plugin-api-docs": "0.11.7", + "@backstage/plugin-api-docs": "0.11.8", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.7", - "@backstage/plugin-app-backend": "0.3.71", - "@backstage/plugin-app-node": "0.1.22", - "@backstage/plugin-app-visualizer": "0.1.8", - "@backstage/plugin-auth-backend": "0.22.9", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.2.3", - "@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-guest-provider": "0.1.8", - "@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-auth-backend-module-pinniped-provider": "0.1.16", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.2.3", - "@backstage/plugin-auth-node": "0.4.17", - "@backstage/plugin-auth-react": "0.1.4", - "@backstage/plugin-bitbucket-cloud-common": "0.2.21", - "@backstage/plugin-catalog": "1.21.1", - "@backstage/plugin-catalog-backend": "1.24.0", - "@backstage/plugin-catalog-backend-module-aws": "0.3.17", - "@backstage/plugin-catalog-backend-module-azure": "0.1.42", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.2.5", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.2.9", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.36", - "@backstage/plugin-catalog-backend-module-gcp": "0.1.23", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.39", - "@backstage/plugin-catalog-backend-module-github": "0.6.5", - "@backstage/plugin-catalog-backend-module-github-org": "0.1.17", - "@backstage/plugin-catalog-backend-module-gitlab": "0.3.21", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.5", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.27", - "@backstage/plugin-catalog-backend-module-ldap": "0.7.0", - "@backstage/plugin-catalog-backend-module-logs": "0.0.1", - "@backstage/plugin-catalog-backend-module-msgraph": "0.5.30", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.40", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.28", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.20", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.4.9", - "@backstage/plugin-catalog-common": "1.0.25", - "@backstage/plugin-catalog-graph": "0.4.7", - "@backstage/plugin-catalog-import": "0.12.1", - "@backstage/plugin-catalog-node": "1.12.4", - "@backstage/plugin-catalog-react": "1.12.2", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.6", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.3", - "@backstage/plugin-config-schema": "0.1.57", - "@backstage/plugin-devtools": "0.1.16", - "@backstage/plugin-devtools-backend": "0.3.8", - "@backstage/plugin-devtools-common": "0.1.11", - "@backstage/plugin-events-backend": "0.3.9", - "@backstage/plugin-events-backend-module-aws-sqs": "0.3.8", - "@backstage/plugin-events-backend-module-azure": "0.2.8", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.8", - "@backstage/plugin-events-backend-module-gerrit": "0.2.8", - "@backstage/plugin-events-backend-module-github": "0.2.8", - "@backstage/plugin-events-backend-module-gitlab": "0.2.8", - "@backstage/plugin-events-backend-test-utils": "0.1.32", - "@backstage/plugin-events-node": "0.3.8", - "@internal/plugin-todo-list": "1.0.29", - "@internal/plugin-todo-list-backend": "1.0.29", - "@internal/plugin-todo-list-common": "1.0.20", - "@backstage/plugin-home": "0.7.7", - "@backstage/plugin-home-react": "0.1.15", - "@backstage/plugin-kubernetes": "0.11.12", - "@backstage/plugin-kubernetes-backend": "0.18.3", - "@backstage/plugin-kubernetes-cluster": "0.0.13", - "@backstage/plugin-kubernetes-common": "0.8.1", - "@backstage/plugin-kubernetes-node": "0.1.16", - "@backstage/plugin-kubernetes-react": "0.4.1", - "@backstage/plugin-notifications": "0.2.3", - "@backstage/plugin-notifications-backend": "0.3.3", - "@backstage/plugin-notifications-backend-module-email": "0.1.3", + "@backstage/plugin-app-backend": "0.3.72", + "@backstage/plugin-app-node": "0.1.23", + "@backstage/plugin-app-visualizer": "0.1.9", + "@backstage/plugin-auth-backend": "0.22.10", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.1.15", + "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.2.0", + "@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-guest-provider": "0.1.9", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.18", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.16", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-okta-provider": "0.0.16", + "@backstage/plugin-auth-backend-module-onelogin-provider": "0.1.4", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.17", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.2.4", + "@backstage/plugin-auth-node": "0.5.0", + "@backstage/plugin-auth-react": "0.1.5", + "@backstage/plugin-bitbucket-cloud-common": "0.2.22", + "@backstage/plugin-catalog": "1.22.0", + "@backstage/plugin-catalog-backend": "1.25.0", + "@backstage/plugin-catalog-backend-module-aws": "0.4.0", + "@backstage/plugin-catalog-backend-module-azure": "0.2.0", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.3.0", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.3.0", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.2.0", + "@backstage/plugin-catalog-backend-module-gcp": "0.2.0", + "@backstage/plugin-catalog-backend-module-gerrit": "0.2.0", + "@backstage/plugin-catalog-backend-module-github": "0.7.0", + "@backstage/plugin-catalog-backend-module-github-org": "0.2.0", + "@backstage/plugin-catalog-backend-module-gitlab": "0.4.0", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.1.0", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.5.0", + "@backstage/plugin-catalog-backend-module-ldap": "0.8.0", + "@backstage/plugin-catalog-backend-module-logs": "0.0.2", + "@backstage/plugin-catalog-backend-module-msgraph": "0.6.0", + "@backstage/plugin-catalog-backend-module-openapi": "0.1.41", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.0", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.21", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.4.10", + "@backstage/plugin-catalog-common": "1.0.26", + "@backstage/plugin-catalog-graph": "0.4.8", + "@backstage/plugin-catalog-import": "0.12.2", + "@backstage/plugin-catalog-node": "1.12.5", + "@backstage/plugin-catalog-react": "1.12.3", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.7", + "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.4", + "@backstage/plugin-config-schema": "0.1.58", + "@backstage/plugin-devtools": "0.1.17", + "@backstage/plugin-devtools-backend": "0.3.9", + "@backstage/plugin-devtools-common": "0.1.12", + "@backstage/plugin-events-backend": "0.3.10", + "@backstage/plugin-events-backend-module-aws-sqs": "0.4.0", + "@backstage/plugin-events-backend-module-azure": "0.2.9", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.9", + "@backstage/plugin-events-backend-module-gerrit": "0.2.9", + "@backstage/plugin-events-backend-module-github": "0.2.9", + "@backstage/plugin-events-backend-module-gitlab": "0.2.9", + "@backstage/plugin-events-backend-test-utils": "0.1.33", + "@backstage/plugin-events-node": "0.3.9", + "@internal/plugin-todo-list": "1.0.30", + "@internal/plugin-todo-list-backend": "1.0.30", + "@internal/plugin-todo-list-common": "1.0.21", + "@backstage/plugin-home": "0.7.9", + "@backstage/plugin-home-react": "0.1.16", + "@backstage/plugin-kubernetes": "0.11.13", + "@backstage/plugin-kubernetes-backend": "0.18.4", + "@backstage/plugin-kubernetes-cluster": "0.0.14", + "@backstage/plugin-kubernetes-common": "0.8.2", + "@backstage/plugin-kubernetes-node": "0.1.17", + "@backstage/plugin-kubernetes-react": "0.4.2", + "@backstage/plugin-notifications": "0.3.0", + "@backstage/plugin-notifications-backend": "0.3.4", + "@backstage/plugin-notifications-backend-module-email": "0.2.0", "@backstage/plugin-notifications-common": "0.0.5", - "@backstage/plugin-notifications-node": "0.2.3", - "@backstage/plugin-org": "0.6.27", - "@backstage/plugin-org-react": "0.1.26", - "@backstage/plugin-permission-backend": "0.5.46", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.19", - "@backstage/plugin-permission-common": "0.8.0", - "@backstage/plugin-permission-node": "0.8.0", - "@backstage/plugin-permission-react": "0.4.24", - "@backstage/plugin-proxy-backend": "0.5.3", - "@backstage/plugin-scaffolder": "1.23.0", - "@backstage/plugin-scaffolder-backend": "1.23.0", - "@backstage/plugin-scaffolder-backend-module-azure": "0.1.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.12", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.23", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.46", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.1.0", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.14", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.1.12", - "@backstage/plugin-scaffolder-backend-module-github": "0.4.0", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.4.4", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.5", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.39", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.30", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.6", - "@backstage/plugin-scaffolder-common": "1.5.4", - "@backstage/plugin-scaffolder-node": "0.4.8", - "@backstage/plugin-scaffolder-node-test-utils": "0.1.9", - "@backstage/plugin-scaffolder-react": "1.10.0", - "@backstage/plugin-search": "1.4.14", - "@backstage/plugin-search-backend": "1.5.14", - "@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-stack-overflow-collator": "0.1.15", - "@backstage/plugin-search-backend-module-techdocs": "0.1.27", - "@backstage/plugin-search-backend-node": "1.2.27", - "@backstage/plugin-search-common": "1.2.13", - "@backstage/plugin-search-react": "1.7.13", - "@backstage/plugin-signals": "0.0.8", - "@backstage/plugin-signals-backend": "0.1.8", - "@backstage/plugin-signals-node": "0.1.8", + "@backstage/plugin-notifications-node": "0.2.4", + "@backstage/plugin-org": "0.6.28", + "@backstage/plugin-org-react": "0.1.27", + "@backstage/plugin-permission-backend": "0.5.47", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.20", + "@backstage/plugin-permission-common": "0.8.1", + "@backstage/plugin-permission-node": "0.8.1", + "@backstage/plugin-permission-react": "0.4.25", + "@backstage/plugin-proxy-backend": "0.5.4", + "@backstage/plugin-scaffolder": "1.24.0", + "@backstage/plugin-scaffolder-backend": "1.24.0", + "@backstage/plugin-scaffolder-backend-module-azure": "0.1.15", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.13", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.13", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.13", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.24", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.47", + "@backstage/plugin-scaffolder-backend-module-gcp": "0.1.1", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.15", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.1.13", + "@backstage/plugin-scaffolder-backend-module-github": "0.4.1", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.4.5", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.6", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.40", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.31", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.7", + "@backstage/plugin-scaffolder-common": "1.5.5", + "@backstage/plugin-scaffolder-node": "0.4.9", + "@backstage/plugin-scaffolder-node-test-utils": "0.1.10", + "@backstage/plugin-scaffolder-react": "1.11.0", + "@backstage/plugin-search": "1.4.15", + "@backstage/plugin-search-backend": "1.5.15", + "@backstage/plugin-search-backend-module-catalog": "0.2.0", + "@backstage/plugin-search-backend-module-elasticsearch": "1.5.4", + "@backstage/plugin-search-backend-module-explore": "0.2.0", + "@backstage/plugin-search-backend-module-pg": "0.5.33", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.2.0", + "@backstage/plugin-search-backend-module-techdocs": "0.2.0", + "@backstage/plugin-search-backend-node": "1.3.0", + "@backstage/plugin-search-common": "1.2.14", + "@backstage/plugin-search-react": "1.7.14", + "@backstage/plugin-signals": "0.0.9", + "@backstage/plugin-signals-backend": "0.1.9", + "@backstage/plugin-signals-node": "0.1.9", "@backstage/plugin-signals-react": "0.0.4", - "@backstage/plugin-techdocs": "1.10.7", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.35", - "@backstage/plugin-techdocs-backend": "1.10.9", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.12", - "@backstage/plugin-techdocs-node": "1.12.8", - "@backstage/plugin-techdocs-react": "1.2.6", - "@backstage/plugin-user-settings": "0.8.9", - "@backstage/plugin-user-settings-backend": "0.2.21", + "@backstage/plugin-techdocs": "1.10.8", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.37", + "@backstage/plugin-techdocs-backend": "1.10.10", + "@backstage/plugin-techdocs-common": "0.1.0", + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.13", + "@backstage/plugin-techdocs-node": "1.12.9", + "@backstage/plugin-techdocs-react": "1.2.7", + "@backstage/plugin-user-settings": "0.8.11", + "@backstage/plugin-user-settings-backend": "0.2.22", "@backstage/plugin-user-settings-common": "0.0.1", - "@backstage/plugin-techdocs-common": "0.0.0" + "@backstage/plugin-app": "0.0.0", + "@backstage/frontend-defaults": "0.0.0" }, "changesets": [ - "angry-dolphins-camp", - "big-eagles-grab", - "blue-pumas-cheer", - "breezy-jeans-tie", - "breezy-rings-fly", - "bright-donkeys-buy", - "bright-trainers-brake", - "calm-crabs-drop", - "chilly-days-peel", - "chilly-trains-sleep", - "clever-pans-brake", - "cool-insects-remember", - "cool-schools-vanish", - "create-app-1722413762", - "cuddly-zebras-crash", - "curvy-pillows-joke", - "cyan-shrimps-push", - "dry-squids-tap", - "dull-ghosts-double", - "early-trees-dance", - "eighty-emus-leave", - "eighty-jokes-deny", - "eighty-mirrors-flow", - "fair-hairs-mix", - "fair-pumas-hang", - "fast-bulldogs-relax", - "few-wasps-hug", - "flat-plums-grow", - "forty-ties-agree", - "forty-ties-disagree", - "friendly-cherries-applaud", - "friendly-chicken-cry", - "friendly-feet-refuse", - "gentle-dryers-smile", - "good-steaks-report", - "green-planets-reflect", - "grumpy-owls-suffer", - "healthy-timers-divide", - "heavy-numbers-love", - "hip-fishes-guess", - "hip-hairs-exist", - "khaki-lamps-peel", - "late-games-protect", - "light-pianos-exercise", - "little-bulldogs-guess", - "little-suns-fly", - "mean-apricots-perform", - "metal-planes-nail", - "metal-rice-call", - "mighty-apricots-taste", - "mighty-dolls-retire", - "mighty-geckos-kiss", - "modern-parrots-protect", - "neat-bears-divide", - "neat-gifts-join", - "neat-socks-cheer", - "new-scissors-try", - "nice-peas-retire", - "nine-cherries-decide", - "nine-glasses-nail", - "nine-seahorses-relate", - "ninety-icons-smile", - "odd-books-share", - "olive-books-sort", - "orange-gifts-protect", - "pink-gorillas-brake", - "plenty-tools-exist", - "purple-carrots-crash", - "quick-roses-juggle", - "rare-foxes-compete", - "red-radios-promise", - "renovate-147ac48", - "renovate-f04beb1", - "rich-mugs-dress", - "selfish-bees-think", - "seven-eggs-admire", - "shaggy-dodos-applaud", - "shy-games-poke", - "shy-waves-share", - "silly-candles-sin", - "silly-cycles-tan", - "six-rats-kick", - "slow-ducks-rush", - "slow-ligers-drum", - "slow-toes-jog", - "small-bottles-cough", - "small-ears-poke", - "small-spoons-shout", - "smooth-countries-relate", - "soft-gorillas-refuse", - "spicy-lies-listen", - "spicy-planets-provide", - "spotty-planets-accept", - "strange-papayas-beg", - "strong-otters-compete", - "stupid-dots-relate", - "sweet-oranges-buy", - "swift-kings-sparkle", - "tall-snakes-fix", - "tasty-ads-rescue", - "thick-hotels-know", - "thirty-adults-grab", - "thirty-paws-hope", - "tiny-oranges-pretend", - "tough-goats-hang", - "tough-lies-repair", - "tricky-ducks-juggle", - "two-emus-work", - "violet-jokes-wave", - "warm-monkeys-marry", - "wicked-bobcats-teach", - "wild-eggs-exist", - "wise-spiders-walk", - "witty-bears-behave", - "witty-geese-battle", - "witty-timers-marry", - "young-games-visit", - "young-peaches-shake" + "afraid-fans-cross", + "angry-hotels-warn", + "big-spies-stare", + "blue-forks-cry", + "breezy-flowers-dance", + "brown-actors-clap", + "brown-boxes-arrive", + "brown-worms-sneeze", + "calm-brooms-fail", + "chilled-cameras-change", + "chilly-birds-shout", + "cold-ways-protect", + "cool-actors-sin", + "cool-melons-check", + "cool-poems-hammer", + "cuddly-rocks-invent", + "curly-brooms-raise", + "dry-beers-shake", + "dry-glasses-push", + "dry-monkeys-mate", + "eighty-tables-hope", + "empty-coats-sparkle", + "famous-badgers-drop", + "five-tigers-share", + "flat-kangaroos-push", + "forty-toes-float", + "four-parents-buy", + "four-ties-raise", + "fresh-apes-dress", + "fresh-bears-prove", + "fresh-pumas-clean", + "friendly-days-march", + "friendly-months-march", + "funny-houses-rest", + "fuzzy-feet-exist", + "fuzzy-mails-walk", + "fuzzy-spies-share", + "gentle-hats-act", + "giant-jars-mix", + "gorgeous-scissors-wave", + "green-worms-rescue", + "healthy-moons-drum", + "heavy-suits-judge", + "hip-pandas-think", + "kind-moose-learn", + "kind-walls-speak", + "large-poets-check", + "loud-brooms-pull", + "lucky-sheep-cover", + "metal-garlics-fetch", + "metal-rivers-grin", + "mighty-cheetahs-call", + "mighty-days-kiss", + "nasty-tigers-knock", + "new-poets-unite", + "ninety-mayflies-raise", + "odd-goats-kiss", + "olive-phones-sniff", + "plenty-dragons-know", + "popular-cooks-camp", + "purple-cows-sing", + "purple-glasses-tease", + "quick-suns-swim", + "quiet-spies-clean", + "rare-feet-melt", + "real-pants-rule", + "renovate-53b7d25", + "renovate-546c524", + "renovate-93d032c", + "renovate-b8911c2", + "rich-bees-tickle", + "rotten-crabs-hear", + "serious-cheetahs-help", + "serious-spies-knock", + "sharp-fans-tan", + "sharp-items-study", + "sharp-mayflies-beg", + "shiny-carpets-worried", + "shiny-carpets-worry", + "shiny-zoos-film", + "short-moles-brush", + "silent-eyes-lie", + "six-goats-sort", + "six-humans-guess", + "sixty-rabbits-cheat", + "slimy-chefs-think", + "smart-gifts-report", + "smart-owls-sell", + "spicy-poems-hammer", + "spicy-vans-eat", + "sweet-cows-clean", + "swift-fishes-rush", + "swift-garlics-mix", + "swift-radios-enjoy", + "swift-seahorses-share", + "tall-camels-march", + "tame-hornets-shake", + "tasty-pigs-vanish", + "thick-walls-share", + "tiny-icons-sit", + "tiny-waves-provide", + "tough-peaches-kneel", + "tricky-apricots-film", + "twenty-clouds-melt", + "twenty-queens-grow", + "unlucky-cycles-clean", + "violet-apricots-smoke", + "violet-jokes-tap", + "warm-boxes-grab-2", + "warm-boxes-grab", + "wild-buses-notice", + "wise-forks-play", + "wise-scissors-help", + "witty-years-cry", + "yellow-bees-hope" ] } diff --git a/.changeset/purple-carrots-crash.md b/.changeset/purple-carrots-crash.md deleted file mode 100644 index 62f90677b8..0000000000 --- a/.changeset/purple-carrots-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. diff --git a/.changeset/purple-cows-sing.md b/.changeset/purple-cows-sing.md new file mode 100644 index 0000000000..3391ced648 --- /dev/null +++ b/.changeset/purple-cows-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Removed deprecated `ServiceFactoryOrFunction` type. diff --git a/.changeset/purple-glasses-tease.md b/.changeset/purple-glasses-tease.md new file mode 100644 index 0000000000..5a107c1a2b --- /dev/null +++ b/.changeset/purple-glasses-tease.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +--- + +The following collator factories are deprecated, please [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) to the new backend system and follow the instructions below to install collators via module: + +- `DefaultCatalogCollatorFactory`: https://github.com/backstage/backstage/blob/nbs10/search-deprecate-create-router/plugins/search-backend-module-catalog/README.md#installation; +- `ToolDocumentCollatorFactory`: https://github.com/backstage/backstage/blob/nbs10/search-deprecate-create-router/plugins/search-backend-module-explore/README.md#installation; +- `DefaultTechDocsCollatorFactory`: https://github.com/backstage/backstage/blob/nbs10/search-deprecate-create-router/plugins/search-backend-module-techdocs/README.md#installation. diff --git a/.changeset/quick-roses-juggle.md b/.changeset/quick-roses-juggle.md deleted file mode 100644 index 981bd8591a..0000000000 --- a/.changeset/quick-roses-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-common': minor ---- - -Initial release of the techdocs-common package. diff --git a/.changeset/quick-suns-swim.md b/.changeset/quick-suns-swim.md new file mode 100644 index 0000000000..6113253e22 --- /dev/null +++ b/.changeset/quick-suns-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Accept `ConfigService` instead of `Config` in constructors/factories diff --git a/.changeset/quiet-spies-clean.md b/.changeset/quiet-spies-clean.md new file mode 100644 index 0000000000..ba277f4021 --- /dev/null +++ b/.changeset/quiet-spies-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. diff --git a/.changeset/rare-feet-melt.md b/.changeset/rare-feet-melt.md new file mode 100644 index 0000000000..b28b09a29d --- /dev/null +++ b/.changeset/rare-feet-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings-backend': patch +--- + +Replaced usage of the deprecated identity service with the new HTTP auth service for the new backend system. diff --git a/.changeset/rare-foxes-compete.md b/.changeset/rare-foxes-compete.md deleted file mode 100644 index 64dd9b557e..0000000000 --- a/.changeset/rare-foxes-compete.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -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)}
; - })} -
, - ), - ]; - }, -}); -``` diff --git a/.changeset/real-lizards-sit.md b/.changeset/real-lizards-sit.md deleted file mode 100644 index 747f041508..0000000000 --- a/.changeset/real-lizards-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix undefined in the title of Scaffolder Runs on the page load diff --git a/.changeset/real-pants-rule.md b/.changeset/real-pants-rule.md new file mode 100644 index 0000000000..8d6d3dc708 --- /dev/null +++ b/.changeset/real-pants-rule.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. diff --git a/.changeset/red-radios-promise.md b/.changeset/red-radios-promise.md deleted file mode 100644 index ea876c3480..0000000000 --- a/.changeset/red-radios-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gitlab': patch ---- - -Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. diff --git a/.changeset/renovate-147ac48.md b/.changeset/renovate-147ac48.md deleted file mode 100644 index 257255dff1..0000000000 --- a/.changeset/renovate-147ac48.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@graphiql/react` to `^0.23.0`. diff --git a/.changeset/renovate-53b7d25.md b/.changeset/renovate-53b7d25.md new file mode 100644 index 0000000000..0c4f7255d3 --- /dev/null +++ b/.changeset/renovate-53b7d25.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +Updated dependency `esbuild` to `^0.23.0`. diff --git a/.changeset/renovate-546c524.md b/.changeset/renovate-546c524.md new file mode 100644 index 0000000000..bbe6d8afc5 --- /dev/null +++ b/.changeset/renovate-546c524.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. diff --git a/.changeset/renovate-93d032c.md b/.changeset/renovate-93d032c.md new file mode 100644 index 0000000000..b34e5a2e23 --- /dev/null +++ b/.changeset/renovate-93d032c.md @@ -0,0 +1,5 @@ +--- +'@backstage/codemods': patch +--- + +Updated dependency `jscodeshift` to `^0.16.0`. diff --git a/.changeset/renovate-b8911c2.md b/.changeset/renovate-b8911c2.md new file mode 100644 index 0000000000..9f93a13254 --- /dev/null +++ b/.changeset/renovate-b8911c2.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Updated dependency `use-immer` to `^0.10.0`. diff --git a/.changeset/renovate-f04beb1.md b/.changeset/renovate-f04beb1.md deleted file mode 100644 index 12d24ccc53..0000000000 --- a/.changeset/renovate-f04beb1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-explore': patch ---- - -Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. diff --git a/.changeset/rich-bees-tickle.md b/.changeset/rich-bees-tickle.md new file mode 100644 index 0000000000..a2af89c867 --- /dev/null +++ b/.changeset/rich-bees-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Wrap scheduled tasks from the scheduler core service now in OpenTelemetry spans diff --git a/.changeset/rich-mugs-dress.md b/.changeset/rich-mugs-dress.md deleted file mode 100644 index 5008e33a70..0000000000 --- a/.changeset/rich-mugs-dress.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -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. diff --git a/.changeset/rotten-crabs-hear.md b/.changeset/rotten-crabs-hear.md new file mode 100644 index 0000000000..69148c59c1 --- /dev/null +++ b/.changeset/rotten-crabs-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Validate notification link when new notification is created diff --git a/.changeset/selfish-bees-think.md b/.changeset/selfish-bees-think.md deleted file mode 100644 index 15598ea551..0000000000 --- a/.changeset/selfish-bees-think.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -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`. diff --git a/.changeset/serious-cheetahs-help.md b/.changeset/serious-cheetahs-help.md new file mode 100644 index 0000000000..88bad696c5 --- /dev/null +++ b/.changeset/serious-cheetahs-help.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-catalog': patch +--- + +The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). diff --git a/.changeset/serious-spies-knock.md b/.changeset/serious-spies-knock.md new file mode 100644 index 0000000000..1484753019 --- /dev/null +++ b/.changeset/serious-spies-knock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Skip start without proper config diff --git a/.changeset/seven-days-film.md b/.changeset/seven-days-film.md deleted file mode 100644 index 9d757b0611..0000000000 --- a/.changeset/seven-days-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. diff --git a/.changeset/seven-eggs-admire.md b/.changeset/seven-eggs-admire.md deleted file mode 100644 index d68b38e2d7..0000000000 --- a/.changeset/seven-eggs-admire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data diff --git a/.changeset/shaggy-dodos-applaud.md b/.changeset/shaggy-dodos-applaud.md deleted file mode 100644 index b7f968aa39..0000000000 --- a/.changeset/shaggy-dodos-applaud.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. diff --git a/.changeset/sharp-fans-tan.md b/.changeset/sharp-fans-tan.md new file mode 100644 index 0000000000..add4a85758 --- /dev/null +++ b/.changeset/sharp-fans-tan.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +- Fix secret widget field not displaying as required. +- Fix secret widget not able to be required inside nested objects. +- Fix secret widget not able to be disabled. +- Support `minLength` and `maxLength` properties for secret widget. diff --git a/.changeset/sharp-items-study.md b/.changeset/sharp-items-study.md new file mode 100644 index 0000000000..61e8af54b6 --- /dev/null +++ b/.changeset/sharp-items-study.md @@ -0,0 +1,19 @@ +--- +'@backstage/backend-defaults': patch +--- + +Exports the `discoveryFeatureLoader` as a replacement for the deprecated `featureDiscoveryService`. +The `discoveryFeatureLoader` is a new backend system [feature loader](https://backstage.io/docs/backend-system/architecture/feature-loaders/) that discovers backend features from the current `package.json` and its dependencies. +Here is an example using the `discoveryFeatureLoader` loader in a new backend instance: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { discoveryFeatureLoader } from '@backstage/backend-defaults'; +//... + +const backend = createBackend(); +//... +backend.add(discoveryFeatureLoader); +//... +backend.start(); +``` diff --git a/.changeset/sharp-mayflies-beg.md b/.changeset/sharp-mayflies-beg.md new file mode 100644 index 0000000000..c78335c187 --- /dev/null +++ b/.changeset/sharp-mayflies-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal refactor following removal of v1 extension support. The app implementation itself still supports v1 extensions at runtime. diff --git a/.changeset/shiny-carpets-worried.md b/.changeset/shiny-carpets-worried.md new file mode 100644 index 0000000000..c0093b49aa --- /dev/null +++ b/.changeset/shiny-carpets-worried.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': minor +--- + +**BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. diff --git a/.changeset/shiny-carpets-worry.md b/.changeset/shiny-carpets-worry.md new file mode 100644 index 0000000000..53688df920 --- /dev/null +++ b/.changeset/shiny-carpets-worry.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/frontend-app-api': patch +--- + +Removing deprecated `namespace` parameter in favour of `pluginId` instead diff --git a/.changeset/shiny-zoos-film.md b/.changeset/shiny-zoos-film.md new file mode 100644 index 0000000000..f9fb8cdce5 --- /dev/null +++ b/.changeset/shiny-zoos-film.md @@ -0,0 +1,21 @@ +--- +'@backstage/plugin-signals': patch +--- + +Added a `SignalsDisplay` extension to allows the signals plugin to be installed in an app as follows: + +```tsx +export default app.createRoot( + <> + + + + + + {routes} + + , +); +``` + +With this in place you can remove the explicit installation via the `plugins` option for `createApp`. diff --git a/.changeset/short-moles-brush.md b/.changeset/short-moles-brush.md new file mode 100644 index 0000000000..033805c2b5 --- /dev/null +++ b/.changeset/short-moles-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +`createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. diff --git a/.changeset/shy-games-poke.md b/.changeset/shy-games-poke.md deleted file mode 100644 index 1d2abff39c..0000000000 --- a/.changeset/shy-games-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Add support for stream transport for debugging purposes diff --git a/.changeset/shy-waves-share.md b/.changeset/shy-waves-share.md deleted file mode 100644 index 861590fd01..0000000000 --- a/.changeset/shy-waves-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. diff --git a/.changeset/silent-eyes-lie.md b/.changeset/silent-eyes-lie.md new file mode 100644 index 0000000000..e9da3d2685 --- /dev/null +++ b/.changeset/silent-eyes-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': minor +--- + +Added the icons option to the renderInTestApp function's TestAppOptions to be forwarded to the icons option of `createApp`. diff --git a/.changeset/silly-candles-sin.md b/.changeset/silly-candles-sin.md deleted file mode 100644 index b1d55113fd..0000000000 --- a/.changeset/silly-candles-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -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. diff --git a/.changeset/silly-cycles-tan.md b/.changeset/silly-cycles-tan.md deleted file mode 100644 index 517ce526ea..0000000000 --- a/.changeset/silly-cycles-tan.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. -This was already documented and supported by the plugin. diff --git a/.changeset/silly-scissors-turn.md b/.changeset/silly-scissors-turn.md deleted file mode 100644 index 985a6fb4fd..0000000000 --- a/.changeset/silly-scissors-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Included permission config and enabled it out of the box diff --git a/.changeset/six-goats-sort.md b/.changeset/six-goats-sort.md new file mode 100644 index 0000000000..5df8924066 --- /dev/null +++ b/.changeset/six-goats-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Remove dependency on backend-common diff --git a/.changeset/six-humans-guess.md b/.changeset/six-humans-guess.md new file mode 100644 index 0000000000..60845257c8 --- /dev/null +++ b/.changeset/six-humans-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/.changeset/six-mails-smell.md b/.changeset/six-mails-smell.md deleted file mode 100644 index 3845c72626..0000000000 --- a/.changeset/six-mails-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -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. diff --git a/.changeset/six-rats-kick.md b/.changeset/six-rats-kick.md deleted file mode 100644 index 23958a2502..0000000000 --- a/.changeset/six-rats-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:projectAccessToken:create example diff --git a/.changeset/sixty-rabbits-cheat.md b/.changeset/sixty-rabbits-cheat.md new file mode 100644 index 0000000000..0161e272ed --- /dev/null +++ b/.changeset/sixty-rabbits-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': minor +--- + +Make use of the `useApp` hook to retrieve the specified search icon in the SearchBar diff --git a/.changeset/slimy-chefs-think.md b/.changeset/slimy-chefs-think.md new file mode 100644 index 0000000000..7fa546a399 --- /dev/null +++ b/.changeset/slimy-chefs-think.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Refactor to use injected catalog client in the new backend system diff --git a/.changeset/slow-ducks-rush.md b/.changeset/slow-ducks-rush.md deleted file mode 100644 index c09f883e9c..0000000000 --- a/.changeset/slow-ducks-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-compat-api': patch ---- - -Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. diff --git a/.changeset/slow-ligers-drum.md b/.changeset/slow-ligers-drum.md deleted file mode 100644 index 867cc07cee..0000000000 --- a/.changeset/slow-ligers-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fix null check in `isJsonObject` utility function for scaffolder review state component diff --git a/.changeset/slow-toes-jog.md b/.changeset/slow-toes-jog.md deleted file mode 100644 index 245feab544..0000000000 --- a/.changeset/slow-toes-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish diff --git a/.changeset/small-bottles-cough.md b/.changeset/small-bottles-cough.md deleted file mode 100644 index 40098a84c7..0000000000 --- a/.changeset/small-bottles-cough.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -'@backstage/backend-plugin-api': minor ---- - -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; - }, -})); - -... - -``` diff --git a/.changeset/small-ears-poke.md b/.changeset/small-ears-poke.md deleted file mode 100644 index de906a8643..0000000000 --- a/.changeset/small-ears-poke.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/frontend-plugin-api': minor ---- - -**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`. diff --git a/.changeset/small-spoons-shout.md b/.changeset/small-spoons-shout.md deleted file mode 100644 index 76c209edc6..0000000000 --- a/.changeset/small-spoons-shout.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/cli': minor ---- - -**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. diff --git a/.changeset/smart-gifts-report.md b/.changeset/smart-gifts-report.md new file mode 100644 index 0000000000..fa3c8d96e9 --- /dev/null +++ b/.changeset/smart-gifts-report.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +--- + +Show additional info on DevTools unprocessed entities table + +- Location path (so that it's easier to search the failed entity from the YAML URL) +- Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. diff --git a/.changeset/smart-owls-sell.md b/.changeset/smart-owls-sell.md new file mode 100644 index 0000000000..0a2b555a20 --- /dev/null +++ b/.changeset/smart-owls-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Properly indent the config schema diff --git a/.changeset/smooth-countries-relate.md b/.changeset/smooth-countries-relate.md deleted file mode 100644 index 5e86ee8ab5..0000000000 --- a/.changeset/smooth-countries-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Use `entityPresentationApi` for the node title and the icon. diff --git a/.changeset/soft-gorillas-refuse.md b/.changeset/soft-gorillas-refuse.md deleted file mode 100644 index 323c4d20d2..0000000000 --- a/.changeset/soft-gorillas-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. diff --git a/.changeset/spicy-lies-listen.md b/.changeset/spicy-lies-listen.md deleted file mode 100644 index 6bc5e95846..0000000000 --- a/.changeset/spicy-lies-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field diff --git a/.changeset/spicy-planets-provide.md b/.changeset/spicy-planets-provide.md deleted file mode 100644 index f2929df850..0000000000 --- a/.changeset/spicy-planets-provide.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': minor ---- - -**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; - }, - }); - }, - }); - }, -}); -``` diff --git a/.changeset/spicy-poems-hammer.md b/.changeset/spicy-poems-hammer.md new file mode 100644 index 0000000000..23e86e178e --- /dev/null +++ b/.changeset/spicy-poems-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +As the `@backstage/backend-common` package is deprecated, we have updated the `techdocs-node` package to stop depending on it. diff --git a/.changeset/spicy-vans-eat.md b/.changeset/spicy-vans-eat.md new file mode 100644 index 0000000000..037a5f18e0 --- /dev/null +++ b/.changeset/spicy-vans-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Fix feature loaders in CJS double-default nested builds diff --git a/.changeset/spotty-planets-accept.md b/.changeset/spotty-planets-accept.md deleted file mode 100644 index 7342f89bd1..0000000000 --- a/.changeset/spotty-planets-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Cleaned up codebase of RepoUrlPicker diff --git a/.changeset/strange-papayas-beg.md b/.changeset/strange-papayas-beg.md deleted file mode 100644 index 6ce33fb029..0000000000 --- a/.changeset/strange-papayas-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Added test cases for gitlab:pipeline:trigger examples diff --git a/.changeset/strong-otters-compete.md b/.changeset/strong-otters-compete.md deleted file mode 100644 index fdcb982b88..0000000000 --- a/.changeset/strong-otters-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. diff --git a/.changeset/stupid-dots-relate.md b/.changeset/stupid-dots-relate.md deleted file mode 100644 index a83777bff9..0000000000 --- a/.changeset/stupid-dots-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch ---- - -Add examples for `fetch:cookiecutter` scaffolder action & improve related tests diff --git a/.changeset/sweet-cows-clean.md b/.changeset/sweet-cows-clean.md new file mode 100644 index 0000000000..fa2004186e --- /dev/null +++ b/.changeset/sweet-cows-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added the ability to provide additional `extensions` and `features` to `renderInTestApp` diff --git a/.changeset/sweet-oranges-buy.md b/.changeset/sweet-oranges-buy.md deleted file mode 100644 index d9f3343e3d..0000000000 --- a/.changeset/sweet-oranges-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. diff --git a/.changeset/swift-fishes-rush.md b/.changeset/swift-fishes-rush.md new file mode 100644 index 0000000000..ccfa3a0e40 --- /dev/null +++ b/.changeset/swift-fishes-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. diff --git a/.changeset/swift-garlics-mix.md b/.changeset/swift-garlics-mix.md new file mode 100644 index 0000000000..fc0993be3f --- /dev/null +++ b/.changeset/swift-garlics-mix.md @@ -0,0 +1,63 @@ +--- +'@backstage/backend-app-api': minor +'@backstage/backend-common': minor +'@backstage/backend-defaults': minor +'@backstage/backend-plugin-api': minor +'@backstage/backend-test-utils': minor +'@backstage/plugin-auth-backend-module-atlassian-provider': minor +'@backstage/plugin-auth-backend-module-aws-alb-provider': minor +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': minor +'@backstage/plugin-auth-backend-module-bitbucket-provider': minor +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': minor +'@backstage/plugin-auth-backend-module-gcp-iap-provider': minor +'@backstage/plugin-auth-backend-module-github-provider': minor +'@backstage/plugin-auth-backend-module-gitlab-provider': minor +'@backstage/plugin-auth-backend-module-google-provider': minor +'@backstage/plugin-auth-backend-module-guest-provider': minor +'@backstage/plugin-auth-backend-module-microsoft-provider': minor +'@backstage/plugin-auth-backend-module-oauth2-provider': minor +'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': minor +'@backstage/plugin-auth-backend-module-oidc-provider': minor +'@backstage/plugin-auth-backend-module-okta-provider': minor +'@backstage/plugin-auth-backend-module-onelogin-provider': minor +'@backstage/plugin-auth-backend-module-pinniped-provider': minor +'@backstage/plugin-auth-backend-module-vmware-cloud-provider': minor +'@backstage/plugin-auth-backend': minor +'@backstage/plugin-catalog-backend-module-backstage-openapi': minor +'@backstage/plugin-catalog-backend-module-gcp': minor +'@backstage/plugin-catalog-backend-module-github-org': minor +'@backstage/plugin-catalog-backend-module-gitlab-org': minor +'@backstage/plugin-catalog-backend-module-ldap': minor +'@backstage/plugin-catalog-backend-module-logs': minor +'@backstage/plugin-catalog-backend-module-openapi': minor +'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': minor +'@backstage/plugin-catalog-backend-module-unprocessed': minor +'@backstage/plugin-devtools-backend': minor +'@backstage/plugin-events-node': minor +'@backstage/plugin-notifications-backend-module-email': minor +'@backstage/plugin-notifications-backend': minor +'@backstage/plugin-permission-backend-module-allow-all-policy': minor +'@backstage/plugin-scaffolder-backend-module-azure': minor +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': minor +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': minor +'@backstage/plugin-scaffolder-backend-module-bitbucket': minor +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': minor +'@backstage/plugin-scaffolder-backend-module-cookiecutter': minor +'@backstage/plugin-scaffolder-backend-module-gcp': minor +'@backstage/plugin-scaffolder-backend-module-gerrit': minor +'@backstage/plugin-scaffolder-backend-module-gitea': minor +'@backstage/plugin-scaffolder-backend-module-github': minor +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +'@backstage/plugin-scaffolder-backend-module-notifications': minor +'@backstage/plugin-scaffolder-backend-module-rails': minor +'@backstage/plugin-scaffolder-backend-module-sentry': minor +'@backstage/plugin-scaffolder-backend-module-yeoman': minor +'@backstage/plugin-search-backend-module-stack-overflow-collator': minor +'@backstage/plugin-signals-backend': minor +--- + +**BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + +This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + +As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. diff --git a/.changeset/swift-kings-sparkle.md b/.changeset/swift-kings-sparkle.md deleted file mode 100644 index 84ceae962b..0000000000 --- a/.changeset/swift-kings-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-aws': patch ---- - -`AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` diff --git a/.changeset/swift-radios-enjoy.md b/.changeset/swift-radios-enjoy.md new file mode 100644 index 0000000000..495117e3c6 --- /dev/null +++ b/.changeset/swift-radios-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Internal refactor to re-declare the token manager service which was removed from `@backstage/backend-plugin-api`, but is still supported in this package for backwards compatibility. diff --git a/.changeset/swift-seahorses-share.md b/.changeset/swift-seahorses-share.md new file mode 100644 index 0000000000..deaf2ab042 --- /dev/null +++ b/.changeset/swift-seahorses-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +--- + +Updated the README to include documentation for the new backend support diff --git a/.changeset/tall-camels-march.md b/.changeset/tall-camels-march.md new file mode 100644 index 0000000000..892a8f19af --- /dev/null +++ b/.changeset/tall-camels-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-signals': patch +--- + +Put a name on the `SignalsDisplay` component extension diff --git a/.changeset/tall-snakes-fix.md b/.changeset/tall-snakes-fix.md deleted file mode 100644 index 351c781375..0000000000 --- a/.changeset/tall-snakes-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -fix typo in `getPluginRequestToken` comments diff --git a/.changeset/tame-hornets-shake.md b/.changeset/tame-hornets-shake.md new file mode 100644 index 0000000000..bf96193b55 --- /dev/null +++ b/.changeset/tame-hornets-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Fix an issue where keys with duplicate final key parts are not all displayed in the `ReviewState`. Change the way the keys are formatted to include the full schema path, separated by `>`. diff --git a/.changeset/tasty-ads-rescue.md b/.changeset/tasty-ads-rescue.md deleted file mode 100644 index d522856bdd..0000000000 --- a/.changeset/tasty-ads-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Avoid excessive numbers of error listeners on cache clients diff --git a/.changeset/tasty-pigs-vanish.md b/.changeset/tasty-pigs-vanish.md new file mode 100644 index 0000000000..73556c70d5 --- /dev/null +++ b/.changeset/tasty-pigs-vanish.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Entity presentation api now only fetches fields that are required to display entity title diff --git a/.changeset/ten-penguins-roll.md b/.changeset/ten-penguins-roll.md deleted file mode 100644 index 152bb1524b..0000000000 --- a/.changeset/ten-penguins-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': minor ---- - -By default, set notification as read when opening snackbar or web notification link diff --git a/.changeset/thick-hotels-know.md b/.changeset/thick-hotels-know.md deleted file mode 100644 index f0c7129256..0000000000 --- a/.changeset/thick-hotels-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix scaffolder action `catalog:write` to write to directories that don't already exist diff --git a/.changeset/thick-squids-drive.md b/.changeset/thick-squids-drive.md deleted file mode 100644 index 0e798063c0..0000000000 --- a/.changeset/thick-squids-drive.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch -'@backstage/frontend-test-utils': patch ---- - -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)} - - ); - } -}); - -``` diff --git a/.changeset/thick-walls-share.md b/.changeset/thick-walls-share.md new file mode 100644 index 0000000000..798b1561b4 --- /dev/null +++ b/.changeset/thick-walls-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +--- + +Added test cases for publish:gerrit action examples diff --git a/.changeset/thin-carrots-eat.md b/.changeset/thin-carrots-eat.md deleted file mode 100644 index 570f21f83d..0000000000 --- a/.changeset/thin-carrots-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added examples for action github:pages and improved its test cases diff --git a/.changeset/thirty-adults-grab.md b/.changeset/thirty-adults-grab.md deleted file mode 100644 index ed986a8289..0000000000 --- a/.changeset/thirty-adults-grab.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-catalog-node': patch ---- - -Explicit declare if the service ref accepts `single` or `multiple` implementations. diff --git a/.changeset/thirty-paws-hope.md b/.changeset/thirty-paws-hope.md deleted file mode 100644 index 0de607f331..0000000000 --- a/.changeset/thirty-paws-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -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. diff --git a/.changeset/tiny-dodos-prove-2.md b/.changeset/tiny-dodos-prove-2.md deleted file mode 100644 index 91ac021c67..0000000000 --- a/.changeset/tiny-dodos-prove-2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. diff --git a/.changeset/tiny-dodos-prove.md b/.changeset/tiny-dodos-prove.md deleted file mode 100644 index 6392e5050e..0000000000 --- a/.changeset/tiny-dodos-prove.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -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. diff --git a/.changeset/tiny-icons-sit.md b/.changeset/tiny-icons-sit.md new file mode 100644 index 0000000000..7dc35a46fd --- /dev/null +++ b/.changeset/tiny-icons-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Small internal fix to better work with recent `lodash` versions diff --git a/.changeset/tiny-oranges-pretend.md b/.changeset/tiny-oranges-pretend.md deleted file mode 100644 index 87227fb962..0000000000 --- a/.changeset/tiny-oranges-pretend.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -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', -}); -``` diff --git a/.changeset/tiny-waves-provide.md b/.changeset/tiny-waves-provide.md new file mode 100644 index 0000000000..aabafcfd6b --- /dev/null +++ b/.changeset/tiny-waves-provide.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-plugin-api': patch +'@backstage/backend-defaults': patch +'@backstage/backend-common': patch +--- + +Allow the cache service to accept the human duration format for TTL diff --git a/.changeset/tough-goats-hang.md b/.changeset/tough-goats-hang.md deleted file mode 100644 index 2608c03b2e..0000000000 --- a/.changeset/tough-goats-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket diff --git a/.changeset/tough-lies-repair.md b/.changeset/tough-lies-repair.md deleted file mode 100644 index b2f848e292..0000000000 --- a/.changeset/tough-lies-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host diff --git a/.changeset/tough-peaches-kneel.md b/.changeset/tough-peaches-kneel.md new file mode 100644 index 0000000000..7674da8879 --- /dev/null +++ b/.changeset/tough-peaches-kneel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed an issue with the by-query call, where ordering by a field that does not exist on all entities led to not all results being returned diff --git a/.changeset/tricky-apricots-film.md b/.changeset/tricky-apricots-film.md new file mode 100644 index 0000000000..7b1c28fdb9 --- /dev/null +++ b/.changeset/tricky-apricots-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-signals-react': patch +--- + +Fix for `useSignal` returning the inverse value for `isSignalsAvailable`. diff --git a/.changeset/tricky-ducks-juggle.md b/.changeset/tricky-ducks-juggle.md deleted file mode 100644 index 48c0d215bd..0000000000 --- a/.changeset/tricky-ducks-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added configuration for the `packages` options to config schema diff --git a/.changeset/twenty-clouds-melt.md b/.changeset/twenty-clouds-melt.md new file mode 100644 index 0000000000..dee2b0f8ef --- /dev/null +++ b/.changeset/twenty-clouds-melt.md @@ -0,0 +1,64 @@ +--- +'@backstage/backend-defaults': minor +--- + +**BREAKING**: The default backend instance no longer provides implementations for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. + +If you rely on plugins that still require these services, you can add them to your own backend by re-creating the service reference and factory. + +The following can be used to implement the identity service: + +```ts +import { + coreServices, + createServiceFactory, + createServiceRef, +} from '@backstage/backend-plugin-api'; +import { + DefaultIdentityClient, + IdentityApi, +} from '@backstage/plugin-auth-node'; + +backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.identity' }), + deps: { + discovery: coreServices.discovery, + }, + async factory({ discovery }) { + return DefaultIdentityClient.create({ discovery }); + }, + }), +); +``` + +The following can be used to implement the token manager service: + +```ts +import { ServerTokenManager, TokenManager } from '@backstage/backend-common'; +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createServiceFactory, + createServiceRef, +} from '@backstage/backend-plugin-api'; + +backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.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/.changeset/twenty-queens-grow.md b/.changeset/twenty-queens-grow.md new file mode 100644 index 0000000000..1236488dbe --- /dev/null +++ b/.changeset/twenty-queens-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Added EntityOwnerPicker component to the TemplateListPage to allow filtering on owner diff --git a/.changeset/two-emus-work.md b/.changeset/two-emus-work.md deleted file mode 100644 index 3c60086043..0000000000 --- a/.changeset/two-emus-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -New command now supports setting package license diff --git a/.changeset/unlucky-cycles-clean.md b/.changeset/unlucky-cycles-clean.md new file mode 100644 index 0000000000..5b582dec59 --- /dev/null +++ b/.changeset/unlucky-cycles-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Scaffolder review page shows static amount of asterisks for secret fields. diff --git a/.changeset/violet-apricots-smoke.md b/.changeset/violet-apricots-smoke.md new file mode 100644 index 0000000000..80caf466eb --- /dev/null +++ b/.changeset/violet-apricots-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Remove references to the `@backstage/backend-tasks` in versions of the `create-app` package, as it has been deprecated. diff --git a/.changeset/violet-jokes-tap.md b/.changeset/violet-jokes-tap.md new file mode 100644 index 0000000000..5b79ced6cd --- /dev/null +++ b/.changeset/violet-jokes-tap.md @@ -0,0 +1,21 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +--- + +Added support for defining `replaces` in `createExtensionInput` which will allow extensions to redirect missing `attachTo` points to an input of the created extension. + +```ts +export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + // attachTo: { id: 'app', input: 'themes'} will be redirected to this input instead + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: () { + ... + } +}); +``` diff --git a/.changeset/violet-jokes-wave.md b/.changeset/violet-jokes-wave.md deleted file mode 100644 index 623cff2583..0000000000 --- a/.changeset/violet-jokes-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fix for `repo build --all` not properly detecting the experimental public entry point. diff --git a/.changeset/warm-boxes-grab-2.md b/.changeset/warm-boxes-grab-2.md new file mode 100644 index 0000000000..f1928c8be7 --- /dev/null +++ b/.changeset/warm-boxes-grab-2.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +A new `apis` parameter has been added to `factory` for extensions. This is a way to access utility APIs without being coupled to the React context. diff --git a/.changeset/warm-boxes-grab.md b/.changeset/warm-boxes-grab.md new file mode 100644 index 0000000000..e10dbe086c --- /dev/null +++ b/.changeset/warm-boxes-grab.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Added the `root` extension the replace the `app` extension as the root of the app. diff --git a/.changeset/warm-monkeys-marry.md b/.changeset/warm-monkeys-marry.md deleted file mode 100644 index 5ea8deae01..0000000000 --- a/.changeset/warm-monkeys-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-sentry': patch ---- - -Added test cases for sentry:project:create examples diff --git a/.changeset/wicked-bobcats-teach.md b/.changeset/wicked-bobcats-teach.md deleted file mode 100644 index 041e336f7d..0000000000 --- a/.changeset/wicked-bobcats-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-react': patch ---- - -feat: Hide visibility of CookieAuthRedirect diff --git a/.changeset/wild-buses-notice.md b/.changeset/wild-buses-notice.md new file mode 100644 index 0000000000..3620786aa8 --- /dev/null +++ b/.changeset/wild-buses-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Added the option to skip database migrations by setting `skipMigrations: true` in config. This can be done globally in the database config or by plugin id. diff --git a/.changeset/wild-eggs-exist.md b/.changeset/wild-eggs-exist.md deleted file mode 100644 index 68c49e343b..0000000000 --- a/.changeset/wild-eggs-exist.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -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**. diff --git a/.changeset/wise-forks-play.md b/.changeset/wise-forks-play.md new file mode 100644 index 0000000000..1c8be76d19 --- /dev/null +++ b/.changeset/wise-forks-play.md @@ -0,0 +1,17 @@ +--- +'@backstage/backend-defaults': minor +--- + +**BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + +For the `/database` subpath exports: + +- The deprecated `dropDatabase` function has now been removed, without replacement. +- The deprecated `LegacyRootDatabaseService` type has now been removed. +- The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. +- `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + +For the `/cache` subpath exports: + +- The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. +- Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. diff --git a/.changeset/wise-scissors-help.md b/.changeset/wise-scissors-help.md new file mode 100644 index 0000000000..c8fd2d4c44 --- /dev/null +++ b/.changeset/wise-scissors-help.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/backend-app-api': patch +'@backstage/backend-common': patch +'@backstage/backend-plugin-api': patch +--- + +Updates to the config schema to match reality diff --git a/.changeset/wise-spiders-walk.md b/.changeset/wise-spiders-walk.md deleted file mode 100644 index 65793041c5..0000000000 --- a/.changeset/wise-spiders-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. diff --git a/.changeset/witty-bears-behave.md b/.changeset/witty-bears-behave.md deleted file mode 100644 index 174467b9eb..0000000000 --- a/.changeset/witty-bears-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls diff --git a/.changeset/witty-geese-battle.md b/.changeset/witty-geese-battle.md deleted file mode 100644 index 082784cc8b..0000000000 --- a/.changeset/witty-geese-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. diff --git a/.changeset/witty-queens-run.md b/.changeset/witty-queens-run.md deleted file mode 100644 index 2d869caa91..0000000000 --- a/.changeset/witty-queens-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Notification email processor supports allowing or denying specific email addresses from receiving notifications diff --git a/.changeset/witty-timers-marry.md b/.changeset/witty-timers-marry.md deleted file mode 100644 index 4193acb6fc..0000000000 --- a/.changeset/witty-timers-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': patch ---- - -Added method `listBranchesByRepository` to `BitbucketCloudClient` diff --git a/.changeset/witty-years-cry.md b/.changeset/witty-years-cry.md new file mode 100644 index 0000000000..48c566730b --- /dev/null +++ b/.changeset/witty-years-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-backend': patch +--- + +Deprecated `createRouter` and its router options in favour of the new backend system. diff --git a/.changeset/yellow-bees-hope.md b/.changeset/yellow-bees-hope.md new file mode 100644 index 0000000000..1ea8690c20 --- /dev/null +++ b/.changeset/yellow-bees-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +Removed deprecated `icons` property passing to `createApp` and `createSpecializedApp`. Use `IconBundleBlueprint.make` to create extensions instead and include them in the app. diff --git a/.changeset/young-birds-push.md b/.changeset/young-birds-push.md deleted file mode 100644 index 54afa48aaf..0000000000 --- a/.changeset/young-birds-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -Introduce an optional spec.type attribute on the Domain and System entity kinds diff --git a/.changeset/young-games-visit.md b/.changeset/young-games-visit.md deleted file mode 100644 index 467abcec86..0000000000 --- a/.changeset/young-games-visit.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch -'@backstage/frontend-test-utils': patch -'@backstage/frontend-app-api': patch -'@backstage/core-compat-api': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-search': patch ---- - -Added config input type to the extensions diff --git a/.changeset/young-peaches-shake.md b/.changeset/young-peaches-shake.md deleted file mode 100644 index 75c93f8f3b..0000000000 --- a/.changeset/young-peaches-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': minor ---- - -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). diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5a70a25512..37f54997c6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,22 +1,17 @@ { $schema: 'https://docs.renovatebot.com/renovate-schema.json', - labels: ['dependencies'], - extends: ['config:best-practices', ':gitSignOff'], // do not pin dev dependencies, which are part of the best-practices preset ignorePresets: [':pinDevDependencies', ':pinDigest', 'docker:pinDigests'], - constraints: { // TODO(freben): Remove this later; it addresses a temporary issue in corepack // https://github.com/nodejs/corepack/issues/379 // https://github.com/renovatebot/renovate/discussions/27465 corepack: '0.24.1', }, - - // the default limit are 10 PRs + // the default limit is 10 PRs prConcurrentLimit: 20, - postUpdateOptions: ['yarnDedupeHighest'], rangeStrategy: 'update-lockfile', ignoreDeps: [ @@ -26,19 +21,19 @@ ], packageRules: [ { - matchSourceUrlPrefixes: ['https://github.com/spotify/web-scripts'], groupName: 'Spotify web-scripts monorepo packages', rangeStrategy: 'replace', + matchSourceUrls: ['https://github.com/spotify/web-scripts{/,}**'], }, { - matchSourceUrlPrefixes: ['https://github.com/microsoft/rushstack'], groupName: 'API Extractor / Rush Stack monorepo packages', rangeStrategy: 'replace', + matchSourceUrls: ['https://github.com/microsoft/rushstack{/,}**'], }, { - matchSourceUrlPrefixes: ['https://github.com/gregberge/svgr'], groupName: 'SVGR monorepo packages', rangeStrategy: 'replace', + matchSourceUrls: ['https://github.com/gregberge/svgr{/,}**'], }, // We update yarn packages manually as it's gzip'd and we don't want to pollute the repository too much. { diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index bad61de7ff..320bb2e0ce 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -243,6 +243,7 @@ Mkdocs monorepo Monorepo monorepos +morgan msgraph msw multiton diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index c3886e05b9..abb98c03e1 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -22,7 +22,7 @@ jobs: action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: disable-sudo: true egress-policy: block diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index c39fdf7f7c..12d33b450e 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -33,7 +33,7 @@ jobs: registry-url: https://registry.npmjs.org/ - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: linux-v18 @@ -46,7 +46,7 @@ jobs: cat ${{ github.event_path }} > event.json - name: Upload Artifacts - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 with: name: preview-spec path: | diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index bde8f15606..fcff8bc11a 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 94b4b51848..1d8e29fab0 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -34,7 +34,7 @@ jobs: ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' - name: fetch base run: git fetch --depth 1 origin ${{ github.base_ref }} - - uses: backstage/actions/changeset-feedback@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + - uses: backstage/actions/changeset-feedback@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 name: Generate feedback with: diff-ref: 'origin/master' diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 2aac0a17f6..2bc7940971 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 6cb16937c6..1ffeac1cc0 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index adf0feb926..9b54e6fe6f 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -40,7 +40,7 @@ jobs: name: Test ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 168d6205e4..5cbe2e15a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: name: Install ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -41,7 +41,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -64,7 +64,7 @@ jobs: name: Verify ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -77,7 +77,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -222,7 +222,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index bdbe22aae8..2ac5434f72 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -10,11 +10,11 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit - - uses: backstage/actions/cron@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + - uses: backstage/actions/cron@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 9f305f9a20..86db3795bc 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -37,7 +37,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -51,14 +51,14 @@ jobs: working-directory: ./example-app - name: Login to GitHub Container Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Build and push uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index eafa4a8fcd..ac02794268 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml deleted file mode 100644 index 5488b9a189..0000000000 --- a/.github/workflows/deploy_nightly.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Deploy Nightly Release -on: - schedule: - - cron: '0 2 * * *' # run at 2 AM UTC - -jobs: - build: - if: github.repository == 'backstage/backstage' # prevent running on forks - - runs-on: ubuntu-latest - - env: - CI: true - NODE_OPTIONS: --max-old-space-size=8192 - - steps: - - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: use node.js 18.x - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: 18.x - registry-url: https://registry.npmjs.org/ # Needed for auth - - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 - with: - cache-prefix: ${{ runner.os }}-v18.x - - # No verification done here, only build & publish. If the master branch - # is broken we will see that from those builds, but we still want to push nightly - # builds since upgrading to them is a manual process anyway. - - - name: tsc - run: yarn tsc - - - name: build - run: yarn backstage-cli repo build - - - name: build embedded techdocs app - working-directory: packages/techdocs-cli-embedded-app - run: yarn build - - # Prepares a nightly release version of any package with pending changesets - # Pre-mode is exited if case we're in it, otherwise it has no effect - - name: prepare nightly release - run: | - yarn changeset pre exit || true - yarn changeset version --snapshot nightly - - # Publishes the nightly release to npm, by using tag we make sure the release is - # not flagged as the latest release, which means that people will not get this - # version of the package unless requested explicitly - - name: publish nightly release - run: | - yarn config set -H 'npmAuthToken' "${{secrets.NPM_TOKEN}}" - yarn workspaces foreach -p -j 10 -v --no-private npm publish --access public --tolerate-republish --tag nightly - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Discord notification - if: ${{ failure() }} - uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # 0.3.2 - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} - with: - args: 'Nightly build failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}' diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 7ae5637986..f86c1db4fa 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -1,11 +1,5 @@ name: Deploy Packages on: - workflow_dispatch: - inputs: - force_release: - description: Unconditionally trigger the release job - required: false - type: boolean push: branches: [master, patch/*] @@ -82,7 +76,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -140,7 +134,7 @@ jobs: release: needs: build - if: needs.build.outputs.needs_release == 'true' || inputs.force_release == true + if: needs.build.outputs.needs_release == 'true' runs-on: ubuntu-latest @@ -148,75 +142,16 @@ jobs: matrix: node-version: [18.x] - env: - CI: 'true' - NODE_OPTIONS: --max-old-space-size=8192 - steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org/ # Needed for auth - - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 - with: - cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - - - name: build type declarations - run: yarn tsc:full - - - name: build packages - run: yarn backstage-cli repo build - - - name: build embedded techdocs app - working-directory: packages/techdocs-cli-embedded-app - run: yarn build - - # Publishes current version of packages that are not already present in the registry - - name: publish - run: | - yarn config set -H 'npmAuthToken' "${{secrets.NPM_TOKEN}}" - if [ -f ".changeset/pre.json" ]; then - yarn workspaces foreach -v --no-private npm publish --access public --tolerate-republish --tag next - else - yarn workspaces foreach -v --no-private npm publish --access public --tolerate-republish - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - # Grabs the version in the root package.json and creates a tag on GitHub - - name: Create a release tag - id: create_tag - run: node scripts/create-release-tag.js - env: - GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - # Convert the newly created tag into a release with changelog information - - name: Create release on GitHub - run: node scripts/create-github-release.js ${{ steps.create_tag.outputs.tag_name }} 1 - env: - GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - - name: Dispatch repository event - uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 - with: - token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - event-type: release-published - client-payload: '{"version": "${{ steps.create_tag.outputs.version }}"}' - - # Notify everyone about this great new release :D + # Notify maintainers that a new release is ready to be published - name: Discord notification uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # 0.3.2 env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} - TAG_NAME: ${{ steps.create_tag.outputs.tag_name }} + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MAINTAINERS_WEBHOOK }} with: - args: 'A new release has been published! https://github.com/backstage/backstage/releases/tag/{{TAG_NAME}}' + args: 'A new release is ready to be [published](https://github.com/backstage/publishing/actions/workflows/publish-main.yml) from {{GITHUB_SHA}}' diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index 26e394fd87..c8793d3ab0 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -3,14 +3,20 @@ on: issues: types: [opened] +permissions: + contents: read + jobs: sync: + permissions: + contents: read # for github/issue-labeler to get repo contents + issues: write # for github/issue-labeler to create or remove labels runs-on: ubuntu-latest if: github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index d6f36da686..27a5a78052 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -30,7 +30,7 @@ jobs: run: | mkdir -p ./pr echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 with: name: pr_number-${{ github.event.pull_request.number }} path: pr/ diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml index 1b2b9ee5be..6133fdef8a 100644 --- a/.github/workflows/pr-review-comment.yaml +++ b/.github/workflows/pr-review-comment.yaml @@ -17,7 +17,7 @@ jobs: steps: # Inspired by https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -40,7 +40,7 @@ jobs: const prNumber = artifact.name.slice('pr_number-'.length) core.setOutput('pr-number', prNumber); - - uses: backstage/actions/re-review@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + - uses: backstage/actions/re-review@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index bf7c5cfa8d..510d5242f4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,12 +18,12 @@ jobs: if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit - name: PR sync - uses: backstage/actions/pr-sync@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/pr-sync@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4ef8cc7181..0d22deeca1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -58,7 +58,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: 'Upload artifact' - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14 + uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index da6bd2592d..859f077095 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -25,7 +25,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index ce79efb148..357b7f5cd9 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 62dd7e0c13..898aaa74c7 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -25,7 +25,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@a674369920067381b450d398b27df7039b7ef635 # v0.6.5 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v18.x diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 4ba792d4e0..19072a443b 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index af2e4da251..0369f89648 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -24,12 +24,12 @@ jobs: node-version: 18.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v18.x - name: Create Snyk report - uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # master + uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found with: args: > diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 1bdf0d30cf..99fb8e16bd 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,13 +25,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Monitor and Synchronize Snyk Policies - uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # master + uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master with: command: monitor args: > @@ -46,7 +46,7 @@ jobs: # Above we run the `monitor` command, this runs the `test` command which is # the one that generates the SARIF report that we can upload to GitHub. - name: Create Snyk report - uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # master + uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master continue-on-error: true # To make sure that SARIF upload gets called with: args: > @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14 + uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 3a10083b7e..3a29ba3c79 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml index 6b51713aa1..8d8cb30789 100644 --- a/.github/workflows/verify_accessibility-noop.yml +++ b/.github/workflows/verify_accessibility-noop.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 386306dcb1..fd86075ea0 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -30,7 +30,7 @@ jobs: with: node-version: 18.x - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v18.x - name: run Lighthouse CI diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index b2fb2dc6ec..ea31ea6431 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14 + uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14 + uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14 + uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 2a0c4449a9..8e1a91d850 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-kubernetes-noop.yml b/.github/workflows/verify_e2e-kubernetes-noop.yml index b51c7fd201..544c4e6186 100644 --- a/.github/workflows/verify_e2e-kubernetes-noop.yml +++ b/.github/workflows/verify_e2e-kubernetes-noop.yml @@ -23,7 +23,7 @@ jobs: name: Kubernetes ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 4647505cbf..18eee7e3f2 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -22,7 +22,7 @@ jobs: name: Kubernetes ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -35,7 +35,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index c042bdeac7..024f288255 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -29,7 +29,7 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index fa303ce1c6..1c1ed7a3eb 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -41,7 +41,7 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -58,7 +58,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 41f76a6410..e2788e2d47 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -30,12 +30,12 @@ jobs: name: Techdocs steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml index 299489fcbe..5244a78919 100644 --- a/.github/workflows/verify_e2e-windows-noop.yml +++ b/.github/workflows/verify_e2e-windows-noop.yml @@ -25,7 +25,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index bc782c4bd3..8663e420eb 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -31,7 +31,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -56,7 +56,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.10' @@ -78,7 +78,7 @@ jobs: uses: browser-actions/setup-chrome@803ef6dfb4fdf22089c9563225d95e4a515820a0 # latest - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index ad94f9ece5..3a5899d3cf 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index d8ccaf73e5..daec683da6 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -21,7 +21,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 08dcddde8e..a290b221b2 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -24,7 +24,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -34,7 +34,7 @@ jobs: uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version: 18.x - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5 with: python-version: '3.9' diff --git a/.github/workflows/verify_microsite_accessibility-noop.yml b/.github/workflows/verify_microsite_accessibility-noop.yml index 9f0ecc65be..a3e8e7fdec 100644 --- a/.github/workflows/verify_microsite_accessibility-noop.yml +++ b/.github/workflows/verify_microsite_accessibility-noop.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 220e2633c3..b2c98c1e58 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_storybook-noop.yml b/.github/workflows/verify_storybook-noop.yml index a1a1c1965d..1f9ded9587 100644 --- a/.github/workflows/verify_storybook-noop.yml +++ b/.github/workflows/verify_storybook-noop.yml @@ -28,7 +28,7 @@ jobs: name: Storybook steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index e9da9f0398..9bea2f3dc9 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -28,7 +28,7 @@ jobs: name: Storybook steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit @@ -42,7 +42,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@772cef06641090d0095188e15c85647acdf0c250 # v0.6.11 + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - name: storybook yarn install @@ -51,7 +51,7 @@ jobs: - run: yarn build-storybook - - uses: chromaui/action@fdbe7756d4dbf493e2fbb822df73be7accd07e1c # v11 + - uses: chromaui/action@b984808b772126a9f44b2b7737b131b68a2ede32 # v11 with: token: ${{ secrets.GITHUB_TOKEN }} # projectToken intentionally shared to allow collaborators to run Chromatic on forks diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 109e2ee96e..feb209dc2b 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit diff --git a/ADOPTERS.md b/ADOPTERS.md index 3b534052f1..76114b6965 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -134,7 +134,6 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [SafetyCulture](https://safetyculture.com/) | [@R-cen](https://github.com/R-cen), [@lachlancooper](https://github.com/lachlancooper), [@hkf57](https://github.com/hkf57) | Internal developer portal to provide a centralized place for engineers to see an overview of their team's services and information related to the service from other systems. Initially focused on the software catalog, techdocs and search. | | [Sana Life Science](https://sanalifescience.com) | [Joe Hillyard](mailto:joe@sanalifescience.com) | API Catalog, Tools Management & Control Hub | | [Ndustrial](https://ndustrial.io) | [Jonathan Skubic](mailto:jonathan@ndustrial.io) | Software Project Catalog | -| [TUI Musement](https://www.musement.com/uk/) | [Simone Fumagalli](mailto:simone.fumagalli@musement.com) | We are importing our catalog into it to keep it under control. The next step is start using templates | | [Kambi AB](https://www.kambi.com) | [Martin Norum](mailto:martin.norum@kambi.com) | We want to kick ass at speed, so we're currently building up a catalog of our existing software, and looking into how Backstage can support us in our journey towards autonomous product teams. Both to improve speed to market and operational awareness. | | [ANZ](https://www.anz.com.au/personal/) | [Elliot Jackson](mailto:elliot.jackson@anz.com) | Catalog, tech docs and automation | | [Genie Solutions](https://www.geniesolutionssoftware.com.au) | [Zainab Bagasrawala](mailto:zainabbagasrawala@geniesolutions.com.au) | Developer Portal to track our projects, documentation, observability tools and more | diff --git a/README-zh_Hans.md b/README-zh_Hans.md index 2813ee0793..129c7e7aa4 100644 --- a/README-zh_Hans.md +++ b/README-zh_Hans.md @@ -55,7 +55,7 @@ Backstage 的文档包括: - [Discord 聊天室](https://discord.gg/backstage-687207715902193673) - 获得支持或讨论项目 - [参与贡献 Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - 如果您想做出贡献,请从这里开始 - [RFCs](https://github.com/backstage/backstage/labels/rfc) - 帮助制定技术方向 -- [FAQ](https://backstage.io/docs/FAQ) - n.: 常问问题 +- [FAQ](https://backstage.io/docs/FAQ) - 常问问题 - [行为准则](CODE_OF_CONDUCT.md) - 这是我们的行事方式 - [采纳者](ADOPTERS.md) - 已经在使用 Backstage 的公司 - [博客](https://backstage.io/blog/) - 公告和更新 diff --git a/README.md b/README.md index 6c27d01db2..4cba1082b0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ [![headline](docs/assets/headline.png)](https://backstage.io/) -> [!NOTE] -> 🏖 From July 1st through 16th, due to maintainers being on summer vacations, expect the project to move a little slower than normal, and support to be limited. Normal service will resume after that! 🏝 - # [Backstage](https://backstage.io) English \| [한국어](README-ko_kr.md) \| [中文版](README-zh_Hans.md) \| [Français](README-fr_FR.md) diff --git a/SECURITY.md b/SECURITY.md index 8de1307356..5c6c8106d5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -59,14 +59,14 @@ function writeTemporaryFile(tmpDir: string, name: string, content: string) { If the `name` of the file is controlled by the user, they can for example enter `../../../../etc/hosts` as the name of the file. This can lead to a file being written outside the intended directory, which in turn can be used to inject malicious code or other form of attacks. -The recommended solution to this is to use `resolveSafeChildPath` from `@backstage/backend-common` to resolve the file path instead. It makes sure that the resolved path does not fall outside the provided directory. If you simply want to validate whether a file path is safe, you can use `isChildPath` instead. +The recommended solution to this is to use `resolveSafeChildPath` from `@backstage/backend-plugin-api` to resolve the file path instead. It makes sure that the resolved path does not fall outside the provided directory. If you simply want to validate whether a file path is safe, you can use `isChildPath` instead. The insecure example above should instead be written like this: ```ts // THIS IS GOOD, DO THIS -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs-extra'; function writeTemporaryFile(tmpDir: string, name: string, content: string) { diff --git a/beps/0002-dynamic-frontend-plugins/README.md b/beps/0002-dynamic-frontend-plugins/README.md index 34c6e2d435..db223c959d 100644 --- a/beps/0002-dynamic-frontend-plugins/README.md +++ b/beps/0002-dynamic-frontend-plugins/README.md @@ -301,7 +301,7 @@ An example of [integration with scalprum](https://github.com/backstage/backstage **Dynamic Feature configuration** -The dynamic remote loading can be added directly into the [`createApp`](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/wiring/createApp.tsx#L234) function. +The dynamic remote loading can be added directly into the [`createApp`](https://backstage.io/docs/reference/frontend-defaults.createapp) function. The current `feature` type can be expanded with a `DynamicFrontendFeature` type: @@ -351,7 +351,7 @@ const scalprum = initialize({ }); ``` -Because the [`appLoader`](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/wiring/createApp.tsx#L193) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. +Because the [`appLoader`](https://backstage.io/docs/reference/frontend-defaults.createapp) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. Initializing the dynamic feature is just a case of mapping the `DynamicFrontendFeature` to `FrontendFeature` via Scalprum: diff --git a/beps/0008-docs-personas-framework-portal/README.md b/beps/0008-docs-personas-framework-portal/README.md index 07468b7c4a..1ef073a053 100644 --- a/beps/0008-docs-personas-framework-portal/README.md +++ b/beps/0008-docs-personas-framework-portal/README.md @@ -3,10 +3,11 @@ title: Improved Backstage Documentation with Personas status: provisional authors: - '@waldirmontoya25' - - '@aramissennyeyd' + - '@aramissennyeydd' owners: + - '@backstage/documentation-maintainers' project-areas: - - core + - documentation creation-date: 2024-03-18 --- @@ -23,22 +24,22 @@ creation-date: 2024-03-18 - [Design Details](#design-details) - [Personas](#personas) - [User](#user) - - [Documentation Style](#documentation-style) - - [Administrator](#administrator) - - [Documentation Style](#documentation-style-1) - - [Integrator](#integrator) - - [Documentation Style](#documentation-style-2) + - [Documentation Content](#documentation-content) + - [Operator](#operator) + - [Documentation Content](#documentation-content-1) + - [Builder](#builder) + - [Documentation Content](#documentation-content-2) - [Contributor](#contributor) - - [Documentation Style](#documentation-style-3) + - [Documentation Content](#documentation-content-3) - [Business Stakeholder](#business-stakeholder) - - [Documentation Style](#documentation-style-4) + - [Documentation Content](#documentation-content-4) - [Release Plan](#release-plan) - [Dependencies](#dependencies) - [Example Table of Contents](#example-table-of-contents) ## Summary -This BEP proposes restructuring the Backstage documentation to emphasize the dual nature of Backstage as both a framework for building developer portals and a fully functional developer portal out of the box, as demonstrated by the demo site. The documentation will be divided into two main sections: One focusing on the developer portal that users get with the core plugins, and another on the framework that allows integrators and builders to create their own developer portal. The goal is to improve clarity, navigation, and adoption of Backstage by positioning it as both a ready-to-use developer portal and a framework for building custom developer portals. +This BEP proposes restructuring the Backstage documentation to emphasize the dual nature of Backstage as both a framework for building developer portals and a fully functional developer portal out of the box, as demonstrated by the demo site. The documentation will be divided into two main sections: One focusing on the developer portal that users get with the core plugins, and another on the framework that allows operators and builders to create their own developer portal. The goal is to improve clarity, navigation, and adoption of Backstage by positioning it as both a ready-to-use developer portal and a framework for building custom developer portals. ## Motivation @@ -48,8 +49,8 @@ The current Backstage documentation has been reported to be difficult to navigat - Divide the documentation into two section: Framework and Developer Portal - Define the personas Backstage is targeting -- Structure the documentation to cater the different personas -- Move the existing content to the appropriate section +- Provide complete, clear and easy-to-find instructions for tasks required of personas +- Move the existing content to the appropriate section. ### Non-Goals @@ -63,14 +64,14 @@ The proposed restructuring of the Backstage documentation revolves around two co 1. Positioning Backstage as both a framework to build developer portals and a developer portal itself, and splitting the documentation into two main sections: - Developer Portal: Focusing on the features, configuration, and usage of the developer portal that users get out of the box with the core plugins. - - Framework: Covering the aspects of Backstage as a framework, including guides for integrators and builders who want to create their own developer portal using Backstage. + - Framework: Covering the aspects of Backstage as a framework, including guides for operators and builders who want to create their own developer portal using Backstage. 2. Defining the personas participating in Backstage adoption journeys to improve documentation navigation. The identified personas are: - **End User**: A person who uses Backstage to find information, use plugins, and consume the developer portal. - - **Administrator/Operator**: A person who configures, secures, and deploys the developer portal, manages plugins, and oversees the general administration of the developer portal. - - **Integrator/Builder**: A person who builds plugins, customizes the code and design, and creates custom-built developer portals based on the Backstage framework. This includes developers and designers and anyone adding new functionality to their own Backstage instance. - - **Product Manager/Business stakeholders**: A person who defines the strategy for adopting Backstage, identifies use cases, communicates the value proposition for adopting Backstage and connects the developer portal to the business strategy. + - **Operator**: A person who configures, secures, and deploys the developer portal, manages plugins, and oversees the general administration of the developer portal. + - **Builder**: A person who builds plugins, customizes the code and design, and creates custom-built developer portals based on the Backstage framework. This includes developers and designers and anyone adding new functionality to their own Backstage instance. + - **Business Stakeholder**: A person who defines the strategy for adopting Backstage, identifies use cases, communicates the value proposition for adopting Backstage and connects the developer portal to the business strategy. - **Contributor**: A person who contributes to the Backstage upstream ecosystem. The adoption strategy would be as follows: @@ -90,6 +91,7 @@ The benefits of restructuring the documentation according to these ideas include - The Docs section of the microsite will be divided into two top-level sections: Framework and Developer Portal. - The structure of the Table of Contents will align with the outline proposed in https://github.com/backstage/backstage/issues/21946. +- The developer portal documentation will be the primary entry point. Users should hit this site when navigating to backstage.io/docs. The framework documentation should be hosted similarly, but will be introduced organically in the developer portal documentation when referencing customization and other builder facing tasks. ### Personas @@ -97,7 +99,7 @@ The benefits of restructuring the documentation according to these ideas include Users navigate the developer portal to access tools, information, and plugins essential for their daily tasks. They rely on Backstage to effortlessly find resources, utilize integrations, and connect with other tools and services within their ecosystem. Their interaction is predominantly with the frontend of the portal, where ease of use, accessibility, and relevant content discovery are critical. -##### Documentation Style +##### Documentation Content Documentation for this persona should be about usability of the portal once it is running. For example: @@ -112,13 +114,13 @@ Documentation for this persona should be about usability of the portal once it i - Using available plugins - Step by step tutorials -#### Administrator +#### Operator -Administrators are responsible for the behind-the-scenes technical setup and maintenance of the Backstage portal. This includes deploying the portal, configuring plugins, managing user access, and ensuring the security and performance of the system. They interact with both the frontend and backend, often using command-line tools, administrative dashboards, and configuration files to perform their tasks. +Operators are responsible for the behind-the-scenes technical setup and maintenance of the Backstage portal. This includes deploying the portal, configuring plugins, managing user access, and ensuring the security and performance of the system. They interact with both the frontend and backend, often using command-line tools, administrative dashboards, and configuration files to perform their tasks. -##### Documentation Style +##### Documentation Content -Documentation written for this persona should be DevOps technical, assuming a strong DevOps background. The goal with administrator documentation is to give administrators a strong understanding of how to deploy and manage a Backstage Developer Portal, best practices. For example: +Documentation written for this persona should be DevOps technical, assuming a strong DevOps background. The goal is to give operators a strong understanding of how to deploy and manage a Backstage Developer Portal, best practices. For example: - Installing and upgrading - Configuring @@ -126,17 +128,17 @@ Documentation written for this persona should be DevOps technical, assuming a st - Plugins - Ingesting data (users/groups/components, etc) - Installing plugins -- Implement Git Flows for the Developer portal +- Implement Git Flows for the Developer Portal - Creating Pipelines for Docs generation - Troubleshooting -#### Integrator +#### Builder -Integrators actively work on extending and customizing Backstage. This includes developing new plugins, customizing the UI/UX, and integrating external services or data sources. Their work is deeply technical, involving coding, and engaging with the Backstage community for support and collaboration. They need a deep understanding of the Backstage architecture and APIs, working closely with both the framework's backend and frontend aspects. +Builders actively work on extending and customizing Backstage. This includes developing new plugins, customizing the UI/UX, and integrating external services or data sources. Their work is deeply technical, involving coding, and engaging with the Backstage community for support and collaboration. They need a deep understanding of the Backstage architecture and APIs, working closely with both the framework's backend and frontend aspects. -##### Documentation Style +##### Documentation Content -Documentation written for this persona should be software technical, assuming a strong software background. While we can assume an overall technical knowledge, where possible we should link out to useful guides for the technologies we use, ex: Node.js, express.js, React, etc. The goal with documentation written for integrators is to give them a strong understanding of how to use the Backstage framework to build/evolve a company's Backstage Developer Portal, orient them to get support from the open source community, and prepare them for continuing to deliver value for their Backstage Developer Portal. For example: +Documentation written for this persona should be software technical, assuming a strong software background. While we can assume an overall technical knowledge, where possible we should link out to useful guides for the technologies we use, ex: Node.js, express.js, React, etc. The goal with documentation written for this persona is to give them a strong understanding of how to use the Backstage framework to build/evolve a company's Backstage Developer Portal, orient them to get support from the open source community, and prepare them for continuing to deliver value for their Backstage Developer Portal. For example: - API references - Frontend and Backend systems @@ -151,7 +153,9 @@ Documentation written for this persona should be software technical, assuming a Contributors are involved in the development of the Backstage framework itself. They contribute to the core codebase, develop new features, fix bugs, create documentation and maintain the overall health of the project. They are deeply involved in the open-source community, collaborating with maintainers and other contributors to improve the framework and its ecosystem. -##### Documentation Style +Documentation for the contributor role should _not_ exist on the docs site. It should exist solely in the Github repositories (backstage/backstage, backstage/community-plugins). It can be linked to from the site, but should not have dedicated guides outside of the Github repositories. + +##### Documentation Content The goal with documentation written for contributors is to give them a strong understanding of how to contribute to the Backstage framework, orient them to get support from the open source community, and prepare them for continuing to deliver value for the Backstage framework. For example: @@ -164,7 +168,7 @@ The goal with documentation written for contributors is to give them a strong un Business stakeholders use Backstage to align technical capabilities with business goals, monitoring how features and plugins support operational efficiency, developer satisfaction, and strategic objectives. They are involved in defining the strategy and measuring the impact of the developer portal on the organization. They need to navigate through dashboards, reports, and analytics within Backstage to gather insights and make informed decisions. -##### Documentation Style +##### Documentation Content Documentation written for this persona should be strategic, assuming a strong background in business development and strategy. The goal for business documentation is to give a strong understanding of what Backstage Developer Portal can do for their company, how to deliver value quickly and continuously and guides for pitching or driving Backstage adoption. For example: @@ -175,9 +179,10 @@ Documentation written for this persona should be strategic, assuming a strong ba ## Release Plan -- Release the BEP by 03/24/2024. -- Discuss the changes with the community and gather feedback by 04/24/2024. -- Implement the changes by 04/30/2024. +- [x] Release the BEP (04/21/2024). +- [ ] Discuss the changes with the community and gather feedback. +- [ ] Implement the table of contents changes. +- [ ] Create issues to track reviewing and rewriting existing documentation into these new personas. ## Dependencies @@ -185,20 +190,87 @@ None ## Example Table of Contents -- Overview - - "The overview should introduce users to the concept of Backstage, what an IDP is, how to deliver value, why you should care about DevEx, etc." - - What is Backstage? - - Roadmap - - Vision - - Release and Versioning Policy - - Backstage Threat Model - - Logo assets - - Support and community +- Developer Portal + - Overview + - What is Backstage? + - Roadmap + - Vision + - User personas and use cases + - How this documentation is organized + - Framework + - Developer portal + - Release and Versioning Policy + - Backstage Threat Model + - Logo assets + - Support and community + - Architecture Overview + - Getting Started + - Operator Guides + - Developer Portal + - "How do I deploy, monitor, configure and verify my Backstage Developer Portal?" + - Installing and Configuring + - Database + - Authentication + - Installing plugins + - Customize the design + - Securing + - Deploying in Production + - Integrating with other systems + - Managing + - Monitoring + - Troubleshooting + - Upgrading + - Keeping backstage up to date + - Customizing + - Core Plugins + - "How do I install and configure Backstage Developer Portal with plugins." + - Home Page + - Installing and Configuring + - Software Catalog + - Overview + - The life of an Entity + - Catalog Configuration + - System Model + - YAML file format + - Entity Reference + - Well Known annotations + - Well known relations + - Well known statuses + - Creating the catalog graph + - Software Templates + - Overview + - Configuring + - Adding a new template + - Writing a template + - Built in actions + - TechDocs + - Overview + - Getting Started + - Architecture + - Installing and configuring + - Using Cloud Storage for TechDocs generated files + - Configuring CI/CD to generate and publish TechDocs sites + - TechDocs CLI + - Troubleshooting + - Kubernetes + - Installing and Configuring + - Authentication + - Troubleshooting + - Search + - Business Stakeholder Guides + - "How do I present Backstage to leadership, what are the benefits, why should I care, etc." + - Strategies for adopting + - Use cases + - User Guides + - "How do I use the default OSS Backstage" + - Logging in + - Registering a component + - Creating a new component + - Reference - Framework - Architecture Overview - - "The arch overview should explain how the framework is structured, where plugins and instances fit in and how to understand the current design of Backstage." - Getting Started - - Integrator/Builder Guides + - Builder Guides - Local Development - "Prepare users for how to develop locally, debug problems, run tests, etc." - CLI @@ -280,68 +352,3 @@ None - "How to get started contributing to OSS." - Contributing to Backstage - Reference -- Developer Portal - - Architecture Overview - - Getting Started - - Administrator Guides - - Developer Portal - - "How do I deploy, monitor, configure and verify my Backstage Developer Portal?" - - Installing and Configuring - - Database - - Authentication - - Installing plugins - - Customize the design - - Securing - - Deploying in Production - - Integrating with other systems - - Managing - - Monitoring - - Troubleshooting - - Upgrading - - Keeping backstage up to date - - Customizing - - Core Plugins - - "How do I install and configure Backstage Developer Portal with plugins." - - Home Page - - Installing and Configuring - - Software Catalog - - Overview - - The life of an Entity - - Catalog Configuration - - System Model - - YAML file format - - Entity Reference - - Well Known annotations - - Well known relations - - Well known statuses - - Creating the catalog graph - - Software Templates - - Overview - - Configuring - - Adding a new template - - Writing a template - - Built in actions - - TechDocs - - Overview - - Getting Started - - Architecture - - Installing and configuring - - Using Cloud Storage for TechDocs generated files - - Configuring CI/CD to generate and publish TechDocs sites - - TechDocs CLI - - Troubleshooting - - Kubernetes - - Installing and Configuring - - Authentication - - Troubleshooting - - Search - - Product Manager Guides - - "How do I present Backstage to leadership, what are the benefits, why should I care, etc." - - Strategies for adopting - - Use cases - - User Guides - - "How do I use the default OSS Backstage" - - Logging in - - Registering a component - - Creating a new component - - Reference diff --git a/beps/0010-scaffolder-templating-in-parameters/README.md b/beps/0010-scaffolder-templating-in-parameters/README.md new file mode 100644 index 0000000000..354dc66ea9 --- /dev/null +++ b/beps/0010-scaffolder-templating-in-parameters/README.md @@ -0,0 +1,223 @@ +--- +title: Supporting templating syntax in `parameters` schema +status: provisional +authors: + - '@benjdlambert' +owners: + - '@benjdlambert' + - '@backstage/scaffolder-maintainers' +project-areas: + - scaffolder +creation-date: 2024-03-26 +--- + + + +# BEP: Supporting templating syntax in `parameters` schema + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/16275) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + + + +This BEP proposes to add support for templating syntax in the `parameters` schema of a scaffolder template. +This will allow users to define properties in the JSON Schema which are templated from current values that have been collected from the user already. +This can be useful when you want to use a value that has already been collected as a default value in another field. + +For example: + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: my-template +spec: + parameters: + - title: Some input + description: Get some info from the user + properties: + name: + type: string + default: Test + description: + type: string + default: ${{ parameters.name or "unknown" }}-description +``` + +## Motivation + + + +Inclusive of the initial RFC there's been a swarm of issues that are requesting this feature, and we want to align on the implementation and design of this feature. + +See the following: + +- https://github.com/backstage/backstage/issues/16275 +- https://github.com/backstage/backstage/pull/23283 +- https://github.com/backstage/backstage/issues/19597 +- https://github.com/backstage/backstage/issues/20533 +- https://github.com/backstage/backstage/pull/17746 + +There's some ideas for introducing a templating syntax for both templating into the `parameters` schema, and also being able to pass through some templating strings to underlying field extensions that can use those templating strings. +We want to align here so that we're not going to have those conflict or compete, and create a standard for how to achieve templating in both circumstances. + +### Goals + + + +- This BEP will settle the implementation for the templating of fields into the JSON Schema in the `parameters` section in the scaffolder templates. +- This BEP will settle how to pass through templating strings to underlying field extensions in a non-conflicting way. + +### Non-Goals + + + +## Proposal + +The proposal is to be able to decorate the template schema server side with a context and use that to drive the form rendering client side. + +We can extend the `/parameter-schema` endpoint to accept a `formData` context query parameter which will be a JSON object of the current `formData` state. This in turn allows the scaffolder frontend to repeatedly call the endpoint to get the updated rendered parameter schema. We'll need to turn the endpoint into a `POST` endpoint to accept the form data, but will retain the `GET` version for backwards compatibility. + +## Design Details + +### Example implementation of the `/parameter-schema` endpoint + +```diff +export interface ScaffolderApi { + getTemplateParameterSchema( + templateRef: string, ++ formData?: JsonObject, + ): Promise; +} +``` + +```diff + router +- .get( ++ .post( + '/v2/templates/:namespace/:kind/:name/parameter-schema', + async (req, res) => { + const credentials = await httpAuth.credentials(req); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + const template = await authorizeTemplate( + req.params, + token, + credentials, + ); + + const parameters = [template.spec.parameters ?? []].flat(); ++ const secureTemplater = await SecureTemplater.loadRenderer({ ++ templateFilters: { ++ ...createDefaultFilters({ integrations }), ++ ...additionalTemplateFilters, ++ }, ++ templateGlobals: additionalTemplateGlobals, ++ }); ++ ++ const templatedParameters = parameters.map(parameter => ++ renderTemplateString( ++ parameter, ++ { ++ parameters: req.body.formData, ++ }, ++ secureTemplater, ++ logger, ++ ), ++ ); +``` + +You can see a quick implementation of this in this [branch](https://github.com/backstage/backstage/compare/master...blam/templating-in-parameters) + +### Workaround for the `default` field + +There's a slight issue with the implementation of the `react-jsonschema-form`, which makes things like live updating on things like the `default` field slightly more difficult. +Currently, on first render, the default value is populated and then stored in the `formData` object or the current state, and the default value is never re-evaluated again at a later stage. + +This means that if end users are wanting to set default values with `${{ parameters.myOtherProperty }}`, then they would need to ensure that they are on different steps in the form +as the form would need to be re-rendered, and for performance reasons, we don't want to re-render the form on every `formData` update. + +We could fix this, by implementing custom logic for when the `parameter-schema` is updated, if the updated field is in a `default: *` field, then we replace the previous value with the new value in the `formData` automatically. +This is a pretty ugly workaround, but maybe the only option we have. Also at this point, pretty unsure if this affects any other parts of the `JSONSchema`, and we would also have to implement it for those fields if they exist. + +### Templated error messages + +Templating for `errorMessages` has been solved by using the `ajv-errors` library https://github.com/backstage/backstage/pull/25624, you can see more about [`backrefs` and pointers here](https://ajv.js.org/packages/ajv-errors.html). Any other template strings that will be passed through the underlying components and to be left untemplated should be encapsulated with options instead of passing through raw strings. The below example illustrates an `entityAndName` format, which under the hood, might do something like `${{ parameters.entity }} - ${{ parameters.name }}`, but this implementation never leaks out to the templating language. + +```yaml +parameters: + properties: + ... + description: + type: string + default: Test-description + ui:field: CustomDisplayField + ui:options: + format: entityAndName +``` + +## Release Plan + + + +This change is backwards compatible, and can be released in a minor release. There's no breaking changes to worry about here. + +## Dependencies + + + +## Alternatives + + + +### Templating client side + +- This could lead to confusion as `filters` such as `parseRepoUrl` and `pick` and any custom filters which you define in the backend would not be available in the client side. + +- Also with the limitations of the `default` value being updated only on first render and never re-evaluated, there's no performance benefit of doing things client side anymore. + +### Accept limitation of the `default` field + +Rather than using a workaround to support re-evaluating the `default` field, we could instead accept it as a limitation, and document it as such. + +This is not desirable, as it is likely a very common use-case to want to template the `default` field, leading to a poor template creation experience. diff --git a/beps/0011-event-auditor/README.md b/beps/0011-event-auditor/README.md new file mode 100644 index 0000000000..44006599eb --- /dev/null +++ b/beps/0011-event-auditor/README.md @@ -0,0 +1,187 @@ +--- +title: Event Auditor +status: provisional +authors: + - '@schultzp2020' +owners: +project-areas: + - core +creation-date: 2024-06-04 +--- + +# BEP: Event Auditor + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/23950) + +- [BEP: Event Auditor](#bep-event-auditor) + - [Summary](#summary) + - [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Proposal](#proposal) + - [Design Details](#design-details) + - [Data Model for Audit Events](#data-model-for-audit-events) + - [Actor Details Interface](#actor-details-interface) + - [Audit Request/Response Interfaces](#audit-requestresponse-interfaces) + - [Audit Event Status Interfaces](#audit-event-status-interfaces) + - [EventAuditor Interface](#eventauditor-interface) + - [Release Plan](#release-plan) + - [Dependencies](#dependencies) + - [Alternatives](#alternatives) + +## Summary + +This feature introduces a dedicated system for recording critical security-related actions and events within Backstage. By maintaining a distinct audit event stream, organizations benefit from enhanced security, improved regulatory compliance, efficient incident response, and ensured data integrity. + +## Motivation + +- Strengthen security by tracking user authentication, authorization, data access, and configuration changes. +- Facilitate adherence to regulatory requirements through logging security-sensitive operations. +- Enable efficient forensic analysis and investigation of security incidents. +- Uphold the integrity of critical audit data by implementing robust access controls and tamper-proof measures. + +### Goals + +- Develop a backend service for the audit event stream to record security-critical events. +- Ensure compliance with regulatory requirements through detailed audit event logging. + - Refer to [NIST: Audit and Accountability](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU) + - Refer to [NIST: Non-Repudiation](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU-10) +- Establish a standardized data format for audit events. + - Refer to [NIST: Content of Audit Records](https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=AU-03) +- Provide access to the transport layer for customizable output options. + +### Non-Goals + +- Implementing mechanisms for event storage, analysis, or visualization. +- Addressing security aspects of event storage and access control beyond initial separation from regular events. + +Both of these non-goals can be implemented as separate plugins. + +## Proposal + +The proposal introduces a crucial change to implement a dedicated audit event stream in Backstage. We recommend creating a new backend service using Winston to establish a distinct channel specifically for audit events. This service would act as a wrapper around Winston, providing methods with strict interfaces to ensure uniformity across audit events throughout the Backstage application. By separating the configuration, we can clearly distinguish between regular application events and critical security events. The event format would include mandatory fields relevant to regulatory compliance and security investigations, such as actor, IP address, timestamp, and event details. This standardized format would streamline the analysis and investigation of security-related events. + +Overall, this approach offers several benefits. Separating the configuration allows for the clear distinction of security-critical events, enhancing monitoring and analysis. The standardized data format within the service ensures consistency and facilitates compliance with regulations. Finally, the service methods simplify the process of logging audit events. + +## Design Details + +### Data Model for Audit Events + +To ensure consistency and facilitate regulatory compliance, the proposal suggests creating a shared package that defines a data model for audit events. This model consists of several key components. + +#### Actor Details Interface + +This interface defines the information related to the actor who triggered the logged event. It includes fields like actor ID, IP address, hostname, and user agent. + +```ts +export type ActorDetails = { + actorId?: string; + ip?: string; + hostname?: string; + userAgent?: string; +}; +``` + +#### Audit Request/Response Interfaces + +These interfaces define the structure of request and response data that might be included in the audit event. It's important to note that these interfaces exclude sensitive information like tokens from headers or other irrelevant details to avoid security risks. + +```ts +export type AuditRequest = { + url: string; + method: string; +}; + +export type AuditResponse = { + status: number; +}; +``` + +#### Audit Event Status Interfaces + +These interfaces define the possible statuses for an audit event entry. There are three options: + +```ts +/** + * Indicates the event was successful. + */ +export type AuditEventSuccessStatus = { status: 'succeeded' }; + +/** + * Indicates the event failed and includes details about the encountered errors. + */ +export type AuditEventFailureStatus = { + status: 'failed'; + errors: E[]; +}; + +export type AuditEventStatus = + | AuditEventSuccessStatus + | AuditEventFailureStatus + | undefined; +``` + +#### EventAuditor Interface + +This interface defines the functionalities of an `EventAuditor` class. This class provides methods for: + +- Extracting the actor ID from an Express request (if available). +- Creating detailed audit event information based on provided options. +- Logging an audit event with a specific level (info, debug, warn, or error). + +```ts +/** + * Common fields of an audit event. + * + * @public + */ +export type AuditEventOptions = AuditEventStatus & { + eventName: string; + message: string; + stage: string; + level?: 'info' | 'debug' | 'warn' | 'error'; + metadata?: JsonValue; + response?: AuditResponse; + request?: Request; +} & ({ actorId: string } | { credentials: BackstageCredentials } | undefined); + +export type AuditEvent = { + actor: ActorDetails; + eventName: string; + stage: string; + isAuditLog: true; + request?: AuditRequest; + response?: AuditResponse; +} & AuditLogStatus; + +export interface EventAuditor { + /** + * Processes an express request and obtains the actorId from it. Returns undefined if actorId is not obtainable. + * + * @public + */ + getActorId(request?: Request): Promise; + + /** + * Generates an Audit Event and logs it at the level passed by the user. + * Supports `info`, `debug`, `warn` or `error` level. Defaults to `info` if no level is passed. + * + * Secrets in the metadata field and request body, params and query field should be redacted by the user before passing in the request object + * @public + */ + auditEvent(options: AuditEventOptions): Promise; +} +``` + +## Release Plan + +The release plan involves initially creating a shared audit event package. Following this, the audit event will be implemented in core packages and other plugins. The first targets should be high-priority areas, such as the scaffolder and catalog systems. Since adding the audit event will not disrupt existing functionality, the release plan is simplified. + +## Dependencies + +- `@backstage/types` +- + +## Alternatives + +N/A diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild index 053b4fb492..fa8e42482b 100644 --- a/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild +++ b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild @@ -55,4 +55,4 @@ COPY docker/default.conf.template /etc/nginx/templates/default.conf.template COPY docker/inject-config.sh /docker-entrypoint.d/40-inject-config.sh -ENV PORT 80 +ENV PORT=80 diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild index f0fa4a034b..b07d02c0c8 100644 --- a/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild +++ b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild @@ -36,4 +36,4 @@ COPY docker/default.conf.template /etc/nginx/templates/default.conf.template COPY docker/inject-config.sh /docker-entrypoint.d/40-inject-config.sh -ENV PORT 80 +ENV PORT=80 diff --git a/contrib/docker/minimal-hardened-image/Dockerfile b/contrib/docker/minimal-hardened-image/Dockerfile index 8ee6a3d206..b9435aa8af 100644 --- a/contrib/docker/minimal-hardened-image/Dockerfile +++ b/contrib/docker/minimal-hardened-image/Dockerfile @@ -9,53 +9,79 @@ # # Once the commands have been run, you can build the image using `yarn docker-build` -FROM cgr.dev/chainguard/wolfi-base:latest +# syntax = docker/dockerfile:1.4 -ENV NODE_VERSION 18=~18.19 -ENV PYTHON_VERSION 3.12=~3.12 +# Build Python environment in a separate builder stage +FROM cgr.dev/chainguard/python:latest-dev as python-builder -RUN apk add nodejs-$NODE_VERSION yarn +ENV PATH=/venv/bin:$PATH -# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, -# in which case you should also move better-sqlite3 to "devDependencies" in package.json. -# Additionally, we install dependencies for `techdocs.generator.runIn: local`. -# https://backstage.io/docs/features/techdocs/getting-started#disabling-docker-in-docker-situation-optional -RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ - --mount=type=cache,target=/var/lib/apk,sharing=locked \ +RUN --mount=type=cache,target=/home/nonroot/.cache/pip,uid=65532,gid=65532 \ + python3 -m venv /home/nonroot/venv && \ + /home/nonroot/venv/bin/pip install mkdocs-techdocs-core==1.3.3 + +# Build Node environment in a separate builder stage +FROM cgr.dev/chainguard/wolfi-base:latest as node-builder + +ENV NODE_VERSION="20=~20.11" +ENV NODE_ENV=production + +RUN --mount=type=cache,target=/var/cache/apk,sharing=locked,uid=65532,gid=65532 \ + --mount=type=cache,target=/var/lib/apk,sharing=locked,uid=65532,gid=65532 \ apk update && \ - apk add sqlite-dev python-$PYTHON_VERSION py3-pip python-3-dev py3-setuptools build-base gcc libffi-dev glibc-dev openssl-dev brotli-dev c-ares-dev nghttp2-dev icu-dev zlib-dev gcc-12 libuv-dev && \ - yarn config set python /usr/bin/python3 + apk add nodejs-$NODE_VERSION yarn \ + # Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. + openssl-dev brotli-dev c-ares-dev nghttp2-dev icu-dev zlib-dev gcc-12 libuv-dev build-base -# Set up a virtual environment for mkdocs-techdocs-core. -ENV VIRTUAL_ENV=/opt/venv -RUN python3 -m venv $VIRTUAL_ENV -ENV PATH="$VIRTUAL_ENV/bin:$PATH" - -RUN pip3 install mkdocs-techdocs-core==1.3.3 - -# From here on we use the least-privileged `node` user to run the backend. WORKDIR /app -RUN chown nonroot:nonroot /app +RUN chown -R nonroot:nonroot /app + +RUN mkdir -p /home/nonroot/.yarn/berry && chown -R 65532:65532 /home/nonroot/.yarn/berry + USER nonroot -# This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +COPY --chown=65532:65532 .yarn ./.yarn +COPY --chown=65532:65532 .yarnrc.yml ./ -# Copy over Yarn 3 configuration, release, and plugins -COPY --chown=nonroot:nonroot .yarn ./.yarn -COPY --chown=nonroot:nonroot .yarnrc.yml ./ - -# Copy repo skeleton first, to avoid unnecessary docker cache invalidation. -# The skeleton contains the package.json of each package in the monorepo, -# and along with yarn.lock and the root package.json, that's enough to run yarn install. -COPY --chown=nonroot:nonroot yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY --chown=65532:65532 yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=1000,gid=1000 \ - yarn workspaces focus --all --production +RUN --mount=type=cache,target=/home/nonroot/.yarn/berry/cache,sharing=locked,uid=65532,gid=65532 \ + yarn workspaces focus --all --production && yarn cache clean --all -# Then copy the rest of the backend bundle, along with any other files we might want. -COPY --chown=nonroot:nonroot packages/backend/dist/bundle.tar.gz app-config*.yaml ./ -RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +# Final stage to build the application image +FROM cgr.dev/chainguard/wolfi-base:latest +ENV PYTHON_VERSION="3.12=~3.12" +ENV NODE_VERSION="20=~20.14" +ENV NODE_ENV=production + +RUN --mount=type=cache,target=/var/cache/apk,sharing=locked,uid=65532,gid=65532 \ + --mount=type=cache,target=/var/lib/apk,sharing=locked,uid=65532,gid=65532 \ + apk update && \ + apk add \ + # add node for backstage + nodejs-$NODE_VERSION \ + # add python for backstage techdocs + python-$PYTHON_VERSION \ + # add tini for init process + tini + +WORKDIR /app + +COPY package.json app-config.yaml ./ +ADD packages/backend/dist/skeleton.tar.gz packages/backend/dist/bundle.tar.gz ./ + +RUN chown -R 65532:65532 /app +RUN chown -R 65532:65532 /tmp +USER 65532:65532 + +COPY --from=node-builder --chown=65532:65532 /app/node_modules ./node_modules +COPY --from=python-builder --chown=65532:65532 /home/nonroot/venv /home/nonroot/venv +ENV PATH=/home/nonroot/venv/bin:$PATH + +ENV NODE_OPTIONS="--no-node-snapshot" +ENV GIT_PYTHON_REFRESH="quiet" + +ENTRYPOINT ["tini", "--"] CMD ["node", "packages/backend", "--config", "app-config.yaml"] diff --git a/contrib/docker/minimal-hardened-image/README.md b/contrib/docker/minimal-hardened-image/README.md index 62045a4ded..191c77986c 100644 --- a/contrib/docker/minimal-hardened-image/README.md +++ b/contrib/docker/minimal-hardened-image/README.md @@ -4,6 +4,19 @@ DockerHub images in general did not seem ideal for Backstage as the number of vu The `Dockerfile` in this directory uses a [wolfi-base](https://github.com/wolfi-dev) image from Chainguard Images. This improves the security of the application and reduces false positives in scanners. +## Steps taken + +When converting, I utilized the upstream Dockerfile as a starting point. + +- Multi-stage build - The Dockerfile has been split up into a multistage build which reduces the files, packages, executables, and directories in the final image. + - Size savings = ~900mb + - Reduced attack surface +- Base Image - Swap to [wolfi-base](https://github.com/wolfi-dev) image from Chainguard Images + - Vulnerability Savings = ~239 at the time of updating this README +- Entrypoint - Swap from `node` to `tini` as entrypoint to ensure that the default signal handlers work and zombie processes are handled properly +- Use `ADD` instead of `COPY` in dockerfile to reduce copied compressed files + - When a `rm` is used to remove a compressed file it still makes its way into the final image. Using `ADD` is safe with local files. + ## Pinning Digest To reduce maintenance, the digest of the image has been removed from the `./Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md index deef356d84..3aab9261c9 100644 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md @@ -111,3 +111,7 @@ The `proxy-agent` package can be used as an alternative to `global-agent` (do no ``` 4. Start the backend with `yarn start` + +## Backstage CLI + +The Backstage CLI [versions:bump](https://backstage.io/docs/tooling/cli/commands#versionsbump) command also supports proxies via `global-agent` environment variable configuration. See the [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated/#proxy) docs for more information. diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md index fb4366c7f7..0db97c649e 100644 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ b/contrib/docs/tutorials/prometheus-metrics.md @@ -1,5 +1,8 @@ # Prometheus +> [!NOTE] +> The Prometheus metrics have been marked as deprecated and will be removed at a later point. The recommendation is to use the OpenTelemetry metrics by following the [Setup OpenTelemetry](https://backstage.io/docs/tutorials/setup-opentelemetry) documentation + ## Overview This is a small tutorial that goes over how to setup your Backstage instance to output metrics in a format that can be pulled in by Prometheus. diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 44c00d1ab7..5df8fc334a 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -6,10 +6,7 @@ description: Backstage Utility APIs ## Introduction -Backstage Plugins strive to be self-contained, with as much functionality as -possible residing within the plugin itself and its backend APIs. There will -however always be a need for plugins to communicate outside of its boundaries, -both with other plugins and the app itself. +Backstage plugins strive to be self-contained, with as much functionality as possible residing within the plugin itself and its backend APIs. There will, however, always be a need for plugins to communicate outside of their boundaries, both with other plugins and with the app itself. Backstage provides two primary methods for plugins to communicate across their boundaries in client-side code. The first one being the @@ -61,7 +58,7 @@ Note that there is no explicit type given for embedded, and [`useApi`](../reference/core-plugin-api.useapi.md) is able to infer the type. -Also note that consuming Utility APIs is not limited to plugins, it can be done +Also note that consuming Utility APIs is not limited to plugins; it can be done from any component inside Backstage, including the ones in [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). The only requirement is that they are beneath the `AppProvider` in the react tree. @@ -93,7 +90,7 @@ createApiFactory({ }); ``` -In this example the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) +In this example, the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) is our API, which encapsulates the [`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The [`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single @@ -135,11 +132,11 @@ there is no step that needs to be taken to include these APIs in an app. ### Plugin APIs In addition to the core APIs, plugins can define and export their own APIs. -While doing so they should usually also provide default implementations of their -own APIs, for example, the `catalog` plugin exports `catalogApiRef`, and also +While doing so, they should usually also provide default implementations of their +own APIs; for example, the `catalog` plugin exports `catalogApiRef` and also supplies a default [`ApiFactory`](../reference/core-plugin-api.apifactory.md) of that API using the `CatalogClient`. There is one restriction to plugin-provided -API Factories: plugins may not supply factories for core APIs, trying to do so +API Factories: plugins may not supply factories for core APIs; trying to do so will cause the app to refuse to start. Plugins supply their APIs through the `apis` option of @@ -165,16 +162,16 @@ export const techdocsPlugin = createPlugin({ ### App APIs Lastly, the app itself is the final point where APIs can be added, and what has -the final say in what APIs will be loaded at runtime. The app may override the +the final say in what APIs will be loaded at runtime? The app may override the factories for any of the core or plugin APIs, with the exception of the config, app theme, and identity APIs. These are static APIs that are tied into the -[`createApp`](../reference/app-defaults.createapp.md) implementation, and +[`createApp`](../reference/app-defaults.createapp.md) implementation and therefore not possible to override. Overriding APIs is useful for apps that want to switch out behavior to tailor it -to their environment. In some cases plugins may also export multiple +to their environment. In some cases plugins, may also export multiple implementations of the same API, where they each have their own different -requirements on for example backend storage and surrounding environment. +requirements, for example, backend storage and the surrounding environment. Supplying APIs to the app works just like for plugins: @@ -238,21 +235,14 @@ checked by the type embedded in the ## Defining custom Utility APIs Plugins are free to define their own Utility APIs. Simply define the TypeScript -interface for the API, and create an +interface for the API and create an [`ApiRef`](../reference/core-plugin-api.apiref.md) using [`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also be sure to -provide at least one implementation of the API, and to declare a default factory +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also, be sure to +provide at least one implementation of the API and to declare a default factory for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). -Custom Utility APIs can be either public or private, which is up to the plugin -to choose. Private APIs do not expose an external API surface, and it's -therefore possible to make breaking changes to the API without affecting other -users of the plugin. If an API is made public however, it opens up for other -plugins to make use of the API, and it also makes it possible for users for your -plugin to override the API in the app. It is however important to maintain -backwards compatibility of public APIs, as you may otherwise break apps that are -using your plugin. +Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's therefore possible to make breaking changes to the API without affecting other users of the plugin. If an API is made public, however, it opens up for other plugins to make use of the API, and it also makes it possible for users for your plugin to override the API in the app. It is, however, important to maintain backward compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the [`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index d1dad0b183..8ea8a4ffc0 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -1,13 +1,12 @@ --- id: add-auth-provider -title: Contributing New Providers +title: Contributing New Provider Modules description: Documentation on adding new authentication providers --- :::note Note -The primary audience for this documentation are contributors to the main -Backstage project that want to add support for new authentication providers. +The primary audience for this documentation are contributors that want to add support for new authentication providers. While you can follow it to implement your own custom providers it is much more advanced than using our built-in providers. @@ -122,191 +121,137 @@ due to its comprehensive set of supported authentication ### Quick guide -[1.](#installing-the-dependencies) Install the passport-js based provider -package. +[1.](#create-new-auth-provider-module) Create a new auth provider module -[2.](#create-implementation) Create a new folder structure for the provider. +[3.](#adding-an-oauth-based-provider) or [adding a proxy auth based provider](#creating-proxy-auth-based-provider) depending on your needs. -[3.](#adding-an-oauth-based-provider) Implement the provider, extending the -suitable framework if needed. +[4.](#add-the-provider-to-the-backend) Add the provider to the backend. -[4.](#hook-it-up-to-the-backend) Add the provider to the backend. +### Create new auth provider module -### Installing the dependencies: +In this example we will create auth module for a made up service named foobar. + +Create a new module using `yarn new`, pick `backend-module` and provide `auth-backend` as the plugin ID and `foobar-provider` as the module ID. + +Make sure that the module has the appropriate passport provider as a dependency. ```bash -cd plugins/auth-backend +cd plugins/auth-backend-backend-module-foobar-provider yarn add passport-provider-a yarn add @types/passport-provider-a ``` -### Create implementation - -Make a new folder with the name of the provider following the below file -structure: - -```bash -plugins/auth-backend/src/providers/providerA -├── index.ts -└── provider.ts -``` - -**`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the -provider class which implements a handler for the chosen framework. - ### Adding an OAuth based provider -If we're adding an `OAuth` based provider we would implement the -`OAuthHandlers` interface. By implementing this -interface we can use the `OAuthProvider` class provided by `lib/oauth`, meaning -we don't need to implement the full -`AuthProviderRouteHandlers` interface that providers -otherwise need to implement. +We're then creating a new module that can extend the Auth backend using the `authProvidersExtensionPoint`. -The provider class takes the provider's options as a class parameter. It also -imports the `Strategy` from the passport package. +```ts title="plugins/auth-backend-foobar-provider/src/module.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { providerAuthenticator } from './authenticator'; -```ts -import { Strategy as ProviderAStrategy } from 'passport-provider-a'; - -export type ProviderAProviderOptions = OAuthProviderOptions & { - // extra options here -} - -export class ProviderAAuthProvider implements OAuthHandlers { - private readonly _strategy: ProviderAStrategy; - - constructor(options: ProviderAProviderOptions) { - this._strategy = new ProviderAStrategy( - { - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - passReqToCallback: false, - response_type: 'code', - /// ... etc - } - verifyFunction, // See the "Verify Callback" section - ); - } - - async start() {} - async handler() {} -} -``` - -### Adding an non-OAuth based provider - -An non-`OAuth` based provider could implement -`AuthProviderRouteHandlers` instead. - -```ts -type ProviderAOptions = { - // ... -}; - -export class ProviderAAuthProvider implements AuthProviderRouteHandlers { - private readonly _strategy: ProviderAStrategy; - - constructor(options: ProviderAOptions) { - this._strategy = new ProviderAStrategy( - { - // ... +/** @public */ +export const authModuleFoobarProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'foobar', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, }, - verifyFunction, // See the "Verify Callback" section - ); - } - - async start() {} - async frameHandler() {} - async logout() {} - async refresh() {} // If supported -} -``` - -#### Integration Wrapper - -Each provider exports an object that provides a way to create new instances -of the provider, along with related utilities like predefined sign-in resolvers. - -The object is created using `createAuthProviderIntegration`, with the most -important part being the `create` method that acts as the factory function -for our provider. - -The factory should return an implementation of `AuthProviderFactory`, which -passes in a object with utilities for configuration, logging, token issuing, -etc. The factory should return an implementation of -`AuthProviderRouteHandlers`. - -The factory is what decides the mapping from -[static configuration](../conf/index.md) to the creation of auth providers. For -example, OAuth providers use `OAuthEnvironmentHandler` to allow for multiple -different configurations, one for each environment, which looks like this; - -```ts -export const okta = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - // read options from config - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - - // Use provided auth handler, or create a default one - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), - }); - - // instantiate our OAuthHandlers implementation - const provider = new OktaAuthProvider({ - audience, - clientId, - clientSecret, - callbackUrl, - authHandler, - signInResolver: options?.signIn?.resolver, - resolverContext, + async init({ providers }) { + providers.registerProvider({ + providerId: 'foobar', + factory: createOAuthProviderFactory({ + authenticator: providerAuthenticator, + signInResolverFactories: { + ...commonSignInResolvers, + }, + }), }); - - // Wrap the OAuthHandlers with OAuthProvider, which implements AuthProviderRouteHandlers - return OAuthProvider.fromConfig(globalConfig, provider, { - providerId, - tokenIssuer, - }); - }); - }, - resolvers: { - /** - * Looks up the user by matching their email local part to the entity name. - */ - emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, - - // ... additional predefined resolvers + }, + }); }, }); ``` -The purpose of the different environments is to allow for a single auth-backend -to serve as the authentication service for multiple different frontend -environments, such as local development, staging, and production. +Now let's implement the actual authenticator for our provider using `Strategy` from a passport package. +The authenticator is responsible for creating the passport strategy and handling the authentication flow using secrets from the config file. + +```ts title="plugins/auth-backend-foobar-provider/src/authenticator.ts" +import { Strategy as ProviderStrategy } from 'passport-provider-a'; +import { + createOAuthAuthenticator, + PassportOAuthAuthenticatorHelper, + PassportOAuthDoneCallback, + PassportProfile, +} from '@backstage/plugin-auth-node'; + +/** @public */ +export const providerAuthenticator = createOAuthAuthenticator({ + defaultProfileTransform: + PassportOAuthAuthenticatorHelper.defaultProfileTransform, + scopes: { + // Scopes required by the provider + required: ['openid', 'email', 'profile', 'offline_access'], + }, + initialize({ callbackUrl, config }) { + const clientId = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + + return PassportOAuthAuthenticatorHelper.from( + new ProviderStrategy( + { + clientID: clientId, + clientSecret: clientSecret, + // ... other options + }, + ( + accessToken: string, + refreshToken: string, + params: any, + fullProfile: PassportProfile, + done: PassportOAuthDoneCallback, + ) => { + done( + undefined, + { fullProfile, params, accessToken }, + { refreshToken }, + ); + }, + ), + ); + }, + + async start(input, helper) { + return helper.start(input); + }, + + async authenticate(input, helper) { + return helper.authenticate(input); + }, + + async refresh(input, helper) { + return helper.refresh(input); + }, +}); +``` + +Here are some examples of authenticators that are already implemented in the codebase: + +- [Google](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-google-provider/src/authenticator.ts) +- [Github](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-github-provider/src/authenticator.ts) +- [Okta](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-okta-provider/src/authenticator.ts) + +### Creating proxy auth based provider + +A proxy auth provider is a provider that uses another provider to authenticate for example Google IAP or AWS ALB, please note that those providers are already supported by Backstage. + +The implementation is similar to the OAuth provider, but the `authenticator` function is different. There are already some examples on how to implement a proxy provider in the codebase, for example [auth-backend-module-gcp-iap-provider](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-gcp-iap-provider) and [auth-backend-module-aws-alb-provider](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-aws-alb-provider) #### Verify Callback @@ -323,27 +268,20 @@ environments, such as local development, staging, and production. > > http://www.passportjs.org/docs/configure/ -**`plugins/auth-backend/src/providers/providerA/index.ts`** is simply -re-exporting the factory function to be used for hooking the provider up to the -backend. +### Add the provider to the backend + +The process for adding the new module is the same as for any other type of module or backend plugin. + +If this provider is internal to your installation the import path that you add to `packages/backend/src/index.ts` would be something like: ```ts -export { createProviderAProvider } from './provider'; +backend.add(import('@internal/plugin-auth-backend-module-foobar-provider')); ``` -### Hook it up to the backend - -**`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` -starts it sets up routing for all the available providers by calling -the factory function of each provider. You need to import the factory -function from the provider and add it to the factory: +But if this module is contributed directly to Backstage the module would be imported as ```ts -import { createProviderAProvider } from './providerA'; - -const factories: { [providerId: string]: AuthProviderFactory } = { - providerA: createProviderAProvider, -}; +backend.add(import('@backstage/plugin-auth-backend-module-foobar-provider')); ``` By doing this `auth-backend` automatically adds these endpoints: diff --git a/docs/auth/aws-alb/provider.md b/docs/auth/aws-alb/provider.md index 8f21b7dbf0..a3974c895e 100644 --- a/docs/auth/aws-alb/provider.md +++ b/docs/auth/aws-alb/provider.md @@ -11,14 +11,18 @@ and get the user seamlessly authenticated. ## Configuration The provider configuration can be added to your `app-config.yaml` under the root -`auth` configuration: +`auth` configuration, similar to the following example: ```yaml title="app-config.yaml" auth: providers: awsalb: - issuer: 'https://example.okta.com/oauth2/default' # optional - region: 'us-west-2' # required, use your actual region here + # 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 @@ -26,6 +30,8 @@ auth: - resolver: emailLocalPartMatchingUserEntityName ``` +Ensure that you have set the signer correctly. It is also recommended that you restrict your target groups' security policy to only accept connections from that ALB. + ### Resolvers This provider includes several resolvers out of the box that you can use: diff --git a/docs/auth/cloudflare/provider.md b/docs/auth/cloudflare/provider.md index 516ea9d647..a1946580a7 100644 --- a/docs/auth/cloudflare/provider.md +++ b/docs/auth/cloudflare/provider.md @@ -32,6 +32,12 @@ auth: serviceTokens: - token: '1uh2fh19efvfh129f1f919u21f2f19jf2.access' subject: 'bot-user@your-company.com' + # You can customize the header name that contains the jwt token, by default + # cf-access-jwt-assertion is used + jwtHeaderName: + # You can customize the authorization cookie name, by default + # CF_Authorization is used + authorizationCookieName: # This picks what sign in resolver(s) you want to use. signIn: resolvers: diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 6479ac7268..298f212399 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -24,7 +24,7 @@ Let's start by adding the following `auth` configuration in your ```yaml auth: providers: - gcp-iap: + gcpIap: audience: '/projects//global/backendServices/' jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT signIn: @@ -77,6 +77,6 @@ backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); ## Adding the provider to the Backstage frontend -See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `gcp-iap` as the provider name. +See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `gcpIap` as the provider name. If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely. diff --git a/docs/auth/index.md b/docs/auth/index.md index 5188d7e2a4..de579d039a 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -112,9 +112,16 @@ const app = createApp({ }); ``` -You can also use the `providers` prop to enable multiple sign-in methods, for example +:::note Note -- allowing guest access: +You can configure sign-in to use a redirect flow with no pop-up by adding +`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` + +::: + +### Using Multiple Providers + +You can also use the `providers` prop to enable multiple sign-in methods, for example to allow guest access: ```tsx title="packages/app/src/App.tsx" const app = createApp({ @@ -140,12 +147,53 @@ const app = createApp({ }); ``` -:::note Note +### Conditionally Render Sign In Provider -You can configure sign-in to use a redirect flow with no pop-up by adding -`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` +In the above example you have both Guest and GitHub sign-in options, this is helpful for non-production but in Production you will most likely not want to offer Guest access. You can easily use information from your config to help conditionally render the provider: -::: +```tsx title="packages/app/src/App.tsx" +import { + configApiRef, + githubAuthApiRef, + useApi, +} from '@backstage/core-plugin-api'; + +const app = createApp({ + components: { + SignInPage: props => { + const configApi = useApi(configApiRef); + if (configApi.getString('auth.environment') === 'development') { + return ( + + ); + } + return ( + + ); + }, + }, + // .. +}); +``` ## Sign-In with Proxy Providers diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md index 4b7f893f0b..12c4ec75f5 100644 --- a/docs/backend-system/architecture/05-extension-points.md +++ b/docs/backend-system/architecture/05-extension-points.md @@ -78,7 +78,7 @@ Another pattern that can be used is a type of singleton pattern where the extens ```ts interface ScaffolderTaskRunnerExtensionPoint { - setTaskRunner(taskRunner: TaskRunner): void; + setTaskRunner(taskRunner: SchedulerServiceTaskRunner): void; } ``` diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md index 4dfca00d4e..db78faaced 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -159,8 +159,7 @@ The same applies for modules that perform their own migrations and interact with the database. They will run on the same logical database instance as the target plugin, so care must be taken to choose table names that do not risk colliding with those of the plugin. A recommended naming pattern is `__`, for example the `@backstage/backend-tasks` package creates -tables named `backstage_backend_tasks__
`. If you use the default [`Knex` migration facilities](https://knexjs.org/guide/migrations.html), you will also +name>__
`, for example the scheduler core service creates tables named `backstage_backend_tasks__
`, because it used to be the case that the service lived in a package named `@backstage/backend-tasks`. Things have since moved around a bit, but the effects of the rule are still visible. If you use the default [`Knex` migration facilities](https://knexjs.org/guide/migrations.html), you will also want to make sure that it uses similarly prefixed migration state tables for its internal bookkeeping needs, so they do not collide with the main ones used by the plugin itself. You can do this as follows: diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index addcc72c84..38107f0242 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -31,17 +31,20 @@ import { myPlugin } from './plugin.ts'; describe('myPlugin', () => { it('can serve values from config', async () => { const fakeConfig = { myPlugin: { value: 7 } }; + const mockLogger = mockServices.logger.mock(); const { server } = await startTestBackend({ features: [ myPlugin(), mockServices.rootConfig.factory({ data: fakeConfig }), + mockLogger, ], }); const response = await request(server).get('/api/example/get-value'); expect(response.status).toBe(200); expect(response.body).toEqual({ value: 7 }); + expect(mockLogger.info).toHaveBeenCalledWith('Starting myPlugin'); }); }); ``` @@ -53,6 +56,76 @@ The returned server also has a `port()` method which returns the dynamically bound listening port. You can use this to perform lower level network interactions with the running test service. +### mock services + +The [`mockServices`](https://backstage.io/docs/reference/backend-test-utils.mockservices) object from `@backstage/backend-test-utils` provides service factory functions, and mocks for all core services that you can use to verify interactions between plugin and services. + +All mock services provide a factory function that is sufficient for most tests. Here's an example: + +```ts +const fakeConfig = { myPlugin: { value: 7 } }; +const { server } = await startTestBackend({ + features: [ + // Will provide access to the default urlReaders automatically. + mockServices.urlReader.factory(), + // Some factories accept options, in this example we provide some fake config. + mockServices.rootConfig.factory({ data: fakeConfig }), + ], +}); +``` + +There might be situations where you want to mock a service implementation to verify interactions, in those cases you can use the `mock` function to get a mock object that you can interact with. Here's an example: + +```ts +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { myPlugin } from './plugin.ts'; + +describe('myPlugin', () => { + it('should call use UrlReader', async () => { + const mockReader = mockServices.urlReader.mock(); + + await startTestBackend({ + features: [myPlugin(), mockReader], + }); + + expect(mockReader.readUrl).toHaveBeenCalledWith('https://backstage.io'); + }); + + it('should call use UrlReader again', async () => { + const partialImpl = jest.fn(); + await startTestBackend({ + features: [ + myPlugin(), + // You could also supply partial implementations to the mock function. + mockServices.urlReader.mock({ readUrl: partialImpl }), + ], + }); + expect(partialImpl).toHaveBeenCalledWith('https://backstage.io'); + }); +}); +``` + +Available services: + +- [`auth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.auth/) +- [`cache`](https://backstage.io/docs/reference/backend-test-utils.mockservices.cache/) +- [`database`](https://backstage.io/docs/reference/backend-test-utils.mockservices.database/) +- [`discovery`](https://backstage.io/docs/reference/backend-test-utils.mockservices.discovery/) +- [`events`](https://backstage.io/docs/reference/backend-test-utils.mockservices.events/) +- [`httpAuth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.httpAuth/) +- [`httpRouter`](https://backstage.io/docs/reference/backend-test-utils.mockservices.httpRouter/) +- [`lifecycle`](https://backstage.io/docs/reference/backend-test-utils.mockservices.lifecycle/) +- [`logger`](https://backstage.io/docs/reference/backend-test-utils.mockservices.logger/) +- [`permissions`](https://backstage.io/docs/reference/backend-test-utils.mockservices.permissions/) +- [`rootConfig`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootConfig/) +- [`rootHealth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootHealth/) +- [`rootHttpRouter`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootHttpRouter/) +- [`rootLifecycle`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootLifecycle/) +- [`rootLogger`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootLogger/) +- [`scheduler`](https://backstage.io/docs/reference/backend-test-utils.mockservices.scheduler/) +- [`urlReader`](https://backstage.io/docs/reference/backend-test-utils.mockservices.urlReader/) +- [`userInfo`](https://backstage.io/docs/reference/backend-test-utils.mockservices.userInfo/) + ## Testing Remote Service Interactions If your backend plugin or service interacts with external services using HTTP diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md index 699852c494..8b2ea17de8 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -255,3 +255,67 @@ backend.add( Checkout the [custom service implementations](https://backstage.io/docs/backend-system/building-backends/index#custom-service-implementations) documentation and also the [core service configurations](https://backstage.io/docs/backend-system/core-services/index) page in case you'd like to create your own custom mock factory for one or more services. 3. Now you can finally start your plugin locally by running `yarn start` from the root folder of your plugin. + +## Remove support for the old backend system + +Given that you have followed the guide above to export your new backend plugin the steps to deprecate and remove the old backend plugin are the following: + +### Deprecate public exports other than the default export + +First of all make sure that `createRouter` and `routerOptions` are marked as deprecated to give users time and an indication to migrate to the new system (we recommend deprecating in one release and remove the deprecates in the following one). +This is done by adding a `@deprecated` annotation to the legacy exports. It's worth noting that the plugin can continue using `createRouter` internally but it should not be exported as part of public api. If you are reusing the create router and relative imports in migrated plugins, ensure that you refactor the internal code to remove deprecated imports once the `createRouter` export gets deleted. It is recommended that you avoid the use of `@backstage/backend-common` and `@backstage/backend-tasks` in migrated plugins as they will be deleted together with the ending of support for the legacy system. There are instructions in most of the deprecated imports about how to stop using them once you have migrated to the new backend system. + +```ts title="@backstage/plugin-kubernetes-backend/src/service/router.ts" +import { KubernetesBuilder } from './KubernetesBuilder'; + +/** +* @public +// highlight-add-next-line +* @deprecated Please migrate to the new backend system. +*/ +export interface RouterOptions { + logger: Logger; + config: Config; + catalogApi: CatalogApi; + clusterSupplier?: KubernetesClustersSupplier; + discovery: PluginEndpointDiscovery; +} + +/** +* @public +// highlight-add-next-line +* @deprecated Please migrate to the new backend system. +*/ +export async function createRouter( + options: RouterOptions, +): Promise { + const { router } = await KubernetesBuilder.createBuilder(options) + .setClusterSupplier(options.clusterSupplier) + .build(); + return router; +} +``` + +If your plugin contains an `api-report.md` file make sure to run `yarn build:api-reports` afterwards. +It's recommended to inspect the api report and look for other exports other than the new backend plugin, they should most likely also be deprecated as plugins in the new backend system are extended using extension points and not directly by passing options. Any type of builder or helper methods that are used together with the backend plugin should be moved to a library package specifically for that plugin (e.g. a `plugin-kubernetes-backend-node` package, see the [package roles](https://backstage.io/docs/tooling/cli/build-system/#package-roles) documentation for more details). + +After removals of deprecations all your `index.ts` should contain is just the default export: + +```ts title="@backstage/plugin-kubernetes-backend/src/index.ts" +export { kubernetesPlugin as default } from './plugin'; +``` + +### Deprecate the `/alpha` subpath if it exists + +In cases where you previously supported the new backend system using an `alpha` export, please deprecate the alpha exports and re-export them from `index.ts`. + +```ts title="@backstage/-backend/src/alpha.ts" +/** +* @alpha +// highlight-add-next-line +* @deprecated Please import from the root path instead. +*/ +export default createPlugin({ + //... +}); +``` diff --git a/docs/backend-system/core-services/database.md b/docs/backend-system/core-services/database.md index e083f0b44d..2a172cbe46 100644 --- a/docs/backend-system/core-services/database.md +++ b/docs/backend-system/core-services/database.md @@ -20,7 +20,7 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; createBackendPlugin({ pluginId: 'example', diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index b3522e1752..88a10455f5 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -52,19 +52,49 @@ You can configure the root HTTP Router service by passing the options to the `cr ```ts import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api'; +import { RequestHandler } from 'express'; +import morgan from 'morgan'; const backend = createBackend(); backend.add( rootHttpRouterServiceFactory({ configure: ({ app, middleware, routes, config, logger, lifecycle }) => { + // Refer to https://expressjs.com/en/guide/writing-middleware.html on how to write express middleware + const customMiddleware = { + logging(): RequestHandler { + const middlewareLogger = logger.child({ + type: 'incomingRequest', + }); + return (req, res, next) => { + // Custom Logging Implementation + next(); + }; + }, + // Default logging middleware uses the [morgan](https://github.com/expressjs/morgan) middleware which you can configure with custom formats. + morganLogging(): RequestHandler { + const middlewareLogger = logger.child({ + type: 'incomingRequest', + }); + const customMorganFormat = + '[:date[clf]] ":method :url HTTP/:http-version" :status ":user-agent"'; + return morgan(customMorganFormat, { + stream: { + write(message: string) { + logger.info(message.trimEnd()); + }, + }, + }); + }, + }; + // the built in middleware is provided through an option in the configure function app.use(middleware.helmet()); app.use(middleware.cors()); app.use(middleware.compression()); // you can add you your own middleware in here - app.use(custom.logging()); + app.use(customMiddleware.logging()); // here the routes that are registered by other plugins app.use(routes); diff --git a/docs/backend-system/core-services/url-reader.md b/docs/backend-system/core-services/url-reader.md index b9616a029e..242ffd2307 100644 --- a/docs/backend-system/core-services/url-reader.md +++ b/docs/backend-system/core-services/url-reader.md @@ -45,3 +45,114 @@ createBackendPlugin({ }, }); ``` + +## Providing custom URL readers + +You can also create an internal or bespoke reader and provide it to the backend using a service factory. The following example shows how to create a custom URL reader and provide it to the backend. + +```ts title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; +import { + ReaderFactory, + urlReaderFactoriesServiceRef, +} from '@backstage/backend-defaults/urlReader'; +import { + createServiceFactory, + UrlReaderService, +} from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; + +class CustomUrlReader implements UrlReaderService { + static factory: ReaderFactory = ({ config, treeResponseFactory }) => { + const reader = new CustomUrlReader(config); + const predicate = (url: URL) => url.host === 'myCustomDomain'; + return [{ reader, predicate }]; + }; + + constructor(private readonly config: Config) {} + // implementations of read, readTree and search methods skipped for this example +} + +const customReader = createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, +}); + +const backend = createBackend(); +// backend.add() of other plugins and modules excluded +backend.add(customReader); +``` + +## Writing URL Readers + +We want to make sure all URL Readers behave in the same way. Hence if possible, +all the methods of the `UrlReaderService` interface should be implemented. However it +is okay to start by implementing just one of them and creating issues for the +remaining ones. + +You can choose to make new URL Readers open source if the use case is beneficial to other users. Either as its own package or by updating the +[`default` factory](https://github.com/backstage/backstage/blob/ce2ca68f07ad3334401d3277b989bf145b728a64/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts#L82-L102) +method of URL Readers. It's recommended to create an issue in the Backstage repository to discuss the use case and get feedback before starting the implementation of a new core URL Reader. + +Here are some general guidelines for writing URL Readers + +#### `readUrl` + +`readUrl` method expects a user-friendly URL, something which can be copied from +the browser naturally when a person is browsing the provider in their browser. + +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/ADOPTERS.md` +- ❌ Not a valid URL : + `https://raw.githubusercontent.com/backstage/backstage/master/ADOPTERS.md` +- ❌ Not a valid URL : `https://github.com/backstage/backstage/ADOPTERS.md` + +Upon receiving the URL, `readUrl` converts the user-friendly URL into an API URL +which can be used to request the provider's API. + +`readUrl` then makes an authenticated request to the provider API and returns the response containing the file's contents and `ETag` (if the provider supports it). + +#### `readTree` + +`readTree` method also expects user-friendly URLs similar to `read` but the URL +should point to a tree (could be the root of a repository or even a +sub-directory). + +- ✅ Valid URL : `https://github.com/backstage/backstage` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/docs` + +Using the provider's API documentation, find out an API endpoint which can be +used to download either a zip or a tarball. You can download the entire tree +(e.g. a repository) and filter out in case the user is expecting only a +sub-tree. But some APIs are smart enough to accept a path and return only a +sub-tree in the downloaded archive. + +#### `search` + +`search` method expects a glob pattern of a URL and returns a list of files +matching the query. + +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/**/catalog-info.yaml` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/**/*.md` +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/*/package.json` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/README` + +The core logic of `readTree` can be used here to extract all the files inside +the tree and return the files matching the pattern in the `url`. + +### Caching + +All of the methods above support ETag based caching. If the method is called +without an ETag, the response contains the ETag of the resource (should ideally +forward the ETag returned by the provider). If the method is called with an +ETag, it first compares the ETag and returns a `NotModifiedError` in case the +resource has not been modified. This approach is very similar to the actual +[`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and +[`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) +HTTP headers. diff --git a/docs/contribute/project-structure.md b/docs/contribute/project-structure.md index 16f20a66ca..f06fba0523 100644 --- a/docs/contribute/project-structure.md +++ b/docs/contribute/project-structure.md @@ -82,9 +82,11 @@ are separated out into their own folder, see further down. package. The `backend` uses plugins to construct a working backend that the frontend (`app`) can use. -- [`backend-common/`](https://github.com/backstage/backstage/tree/master/packages/backend-common) - - There are no "core" packages in the backend. Instead we have `backend-common` - which contains helper middleware and other utils. +- [`backend-app-api/`](https://github.com/backstage/backstage/tree/master/packages/backend-app-api) - + This package contains the central wiring for how to make Backstage backends. + +- [`backend-plugin-api/`](https://github.com/backstage/backstage/tree/master/packages/backend-plugin-api) - + This package contains the core APIs that are used to make Backstage backend features such as plugins and modules. - [`catalog-client`](https://github.com/backstage/backstage/tree/master/packages/catalog-client) - An isomorphic client to interact with the Software Catalog. Backend plugins diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index d47cb248f8..7b71c2eb61 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -81,7 +81,7 @@ USER node WORKDIR /app # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, @@ -246,6 +246,9 @@ WORKDIR /app # Copy the install dependencies from the build stage and context COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./ +# Note: The skeleton bundle only includes package.json files -- if your app has +# plugins that define a `bin` export, the bin files need to be copied as well to +# be linked in node_modules/.bin during yarn install. RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ yarn install --frozen-lockfile --production --network-timeout 600000 @@ -260,7 +263,7 @@ COPY --chown=node:node app-config*.yaml ./ COPY --chown=node:node examples ./examples # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] ``` diff --git a/docs/deployment/flightcontrol.md b/docs/deployment/flightcontrol.md index 5d59371b47..14e0b308a6 100644 --- a/docs/deployment/flightcontrol.md +++ b/docs/deployment/flightcontrol.md @@ -68,7 +68,7 @@ USER node WORKDIR /app # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, diff --git a/docs/features/search/collators.md b/docs/features/search/collators.md new file mode 100644 index 0000000000..27860bfd64 --- /dev/null +++ b/docs/features/search/collators.md @@ -0,0 +1,107 @@ +--- +id: collators +title: Collators +description: Indexing you Backstage content with Collators +--- + +Backstage includes 2 [collators](./concepts.md#collators) out of the box for the [Catalog](#catalog) and [TechDocs](#techdocs). There's also some from the [Backstage Community](#community-collators) too! + +## Catalog + +The Catalog collator will index all the Entities in your Catalog. It is installed by default but if you need to add it manually here's how. + +First we add the plugin into your backend app: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-catalog +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Configuring the Catalog Collator + +The default schedule for the Catalog Collator is to run every 10 minutes, you can provide your own schedule by adding it to your config: + +```yaml title="app-config.yaml +search: + collators: + catalog: + schedule: # same options as in SchedulerServiceTaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + initialDelay: { seconds: 90 } + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 6 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +## TechDocs + +The TechDocs collator will index all the TechDocs in your Catalog. It is installed by default but if you need to add it manually here's how. + +First we add the plugin into your backend app: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-techdocs +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Configuring the TechDocs Collator + +The default schedule for the TechDocs Collator is to run every 10 minutes, you can provide your own schedule by adding it to your config: + +```yaml title="app-config.yaml +search: + collators: + techdocs: + schedule: # same options as in SchedulerServiceTaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + initialDelay: { seconds: 90 } + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 6 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +## Community Collators + +Here are some of the known Search Collators available in from the Backstage Community: + +- [`@backstage/plugin-search-backend-module-explore`](https://github.com/backstage/backstage/tree/master/plugins/search-backend-module-explore): will index content from the [Explore plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/explore/plugins/explore). +- [`@backstage/plugin-search-backend-module-stack-overflow-collator`](https://github.com/backstage/backstage/tree/master/plugins/search-backend-module-stack-overflow-collator): will index content from Stack Overflow. +- [`@backstage-community/search-backend-module-adr`](https://github.com/backstage/community-plugins/tree/main/workspaces/adr/plugins/search-backend-module-adr): will index content from the [ADR plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/adr/plugins/adr). + +## Custom Collators + +To create your own collators/decorators modules, please use the [searchModuleCatalogCollator](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/src/alpha.ts#L49) as an example, we recommend that modules are separated by plugin packages (e.g. `search-backend-module-`). You can also find the available search engines and collator/decorator modules documentation in the Alpha API reports. diff --git a/docs/features/search/concepts.md b/docs/features/search/concepts.md index 69a7223085..a777c2f53c 100644 --- a/docs/features/search/concepts.md +++ b/docs/features/search/concepts.md @@ -86,7 +86,7 @@ Search chooses to completely rebuild indices on a schedule. Different collators can be configured to refresh at different intervals, depending on how often the source information is updated. When search indexing is distributed among multiple backend nodes, coordination to prevent clashes is typically handled by a -distributed `TaskRunner`. +distributed `SchedulerServiceTaskRunner`. ### The Search Page diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index b2a074974f..362b7fc46d 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -133,79 +133,34 @@ For more information about using `Root.tsx`, please see Add the following plugins into your backend app: ```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-node +yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-module-pg @backstage/plugin-search-backend-module-catalog @backstage/plugin-search-backend-module-techdocs ``` -Create a `packages/backend/src/plugins/search.ts` file containing the following -code: +Then add the following lines: -```typescript -import { useHotCleanup } from '@backstage/backend-common'; -import { createRouter } from '@backstage/plugin-search-backend'; -import { - IndexBuilder, - LunrSearchEngine, -} from '@backstage/plugin-search-backend-node'; -import { PluginEnvironment } from '../types'; -import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; -import { Router } from 'express'; +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const searchEngine = new LunrSearchEngine({ - logger: env.logger, - }); - const indexBuilder = new IndexBuilder({ - logger: env.logger, - searchEngine, - }); +// Other plugins... - const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 10 }, - timeout: { minutes: 15 }, - initialDelay: { seconds: 3 }, - }); +/* highlight-add-start */ +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); - indexBuilder.addCollator({ - schedule: every10MinutesSchedule, - factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - tokenManager: env.tokenManager, - }), - }); +// search engines +backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); - const { scheduler } = await indexBuilder.build(); +// search collators +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +/* highlight-add-end */ - scheduler.start(); - useHotCleanup(module, () => scheduler.stop()); - - return await createRouter({ - engine: indexBuilder.getSearchEngine(), - logger: env.logger, - }); -} +backend.start(); ``` -Make the following modifications to your `packages/backend/src/index.ts` file: +With the above setup Search will use the [Lunr](https://github.com/olivernn/lunr.js) in-memory Search Engine but if your have Postgres setup as your database then it will use Postgres as your Search Engine. Learn more in the [Search Engines](./search-engines.md) documentation. -Import the `plugins/search` file you created above: - -```typescript -import search from './plugins/search'; -``` - -Set up an environment for search: - -```typescript -const searchEnv = useHotMemoize(module, () => createEnv('search')); -``` - -Register the search service with the router: - -```typescript -apiRouter.use('/search', await search(searchEnv)); -``` +The above also sets up two Collators for you - Catalog and TechDocs - which will index content from these two locations so that you can easily search them. Learn more in the [Collators documentation](./collators.md). ## Customizing Search @@ -328,7 +283,7 @@ Backstage Search builds and maintains its index [on a schedule](./concepts.md#the-scheduler). You can change how often the indexes are rebuilt for a given type of document. You may want to do this if your documents are updated more or less frequently. You can do so by configuring -a scheduled `TaskRunner` to pass into the `schedule` value, like this: +a scheduled `SchedulerServiceTaskRunner` to pass into the `schedule` value, like this: ```typescript {3} const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ @@ -347,16 +302,19 @@ indexBuilder.addCollator({ ``` Note: if you are using the in-memory Lunr search engine, you probably want to -implement a non-distributed `TaskRunner` like the following to ensure consistency +implement a non-distributed `SchedulerServiceTaskRunner` like the following to ensure consistency if you're running multiple search backend nodes (alternatively, you can configure the search plugin to use a non-distributed database such as [SQLite](../../tutorials/configuring-plugin-databases.md#postgresql-and-sqlite-3)): ```typescript -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; -const schedule: TaskRunner = { - run: async (task: TaskInvocationDefinition) => { +const schedule: SchedulerServiceTaskRunner = { + run: async (task: SchedulerServiceTaskInvocationDefinition) => { const startRefresh = async () => { while (!task.signal?.aborted) { try { diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 9ce30da556..b0df9440f3 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -45,75 +45,6 @@ to do that in two steps. }); ``` -## How to index TechDocs documents - -The TechDocs plugin has supported integrations to Search, meaning that it -provides a default collator factory ready to be used. - -The purpose of this guide is to walk you through how to register the -[DefaultTechDocsCollatorFactory](https://github.com/backstage/backstage/blob/1adc2c7/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts) -in your App, so that you can get TechDocs documents indexed. - -If you have been through the -[Getting Started with Search guide](https://backstage.io/docs/features/search/getting-started), -you should have the `packages/backend/src/plugins/search.ts` file available. If -so, you can go ahead and follow this guide - if not, start by going through the -getting started guide. - -1. Import the `DefaultTechDocsCollatorFactory` from - `@backstage/plugin-search-backend-module-techdocs`. - - ```typescript - import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs'; - ``` - -2. If there isn't an existing schedule you'd like to run the collator on, be - sure to create it first. Something like... - - ```typescript - import { Duration } from 'luxon'; - - const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ seconds: 600 }), - timeout: Duration.fromObject({ seconds: 900 }), - initialDelay: Duration.fromObject({ seconds: 3 }), - }); - ``` - -3. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder. - - ```typescript - indexBuilder.addCollator({ - schedule: every10MinutesSchedule, - factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - logger: env.logger, - tokenManager: env.tokenManager, - }), - }); - ``` - -You should now have your TechDocs documents indexed to your search engine of -choice! - -If you want your users to be able to filter down to the techdocs type when -searching, you can update your `SearchPage.tsx` file in -`packages/app/src/components/search` by adding `techdocs` to the list of values -of the `SearchType` component. - -```tsx title="packages/app/src/components/search/SearchPage.tsx" - - - {/* ... */} - -``` - -> Check out the documentation around [integrating search into plugins](../../plugins/integrating-search-into-plugins.md#create-a-collator) for how to create your own collator. - ## How to customize fields in the Software Catalog or TechDocs index Sometimes, you might want to have the ability to control which data passes into the search index @@ -388,38 +319,3 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => ( ``` There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions). - -## How to migrate your backend installation to use Search together with the new backend system - -Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up. - -In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]: - -```ts -const backend = createBackend(); -// [1] adding the search plugin to the backend -backend.add(import('@backstage/plugin-search-backend/alpha')); -// [2] (optional) the default search engine is Lunr, if you want to extend the search backend with another search engine. -backend.add( - import('@backstage/plugin-search-backend-module-elasticsearch/alpha'), -); -// [3] extending search with collator modules to start index documents, take in optional schedule parameters. -backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); - -backend.start(); -``` - -To create your own collators/decorators modules, please use the [searchModuleCatalogCollator](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/src/alpha.ts#L49) as an example, we recommend that modules are separated by plugin packages (e.g. `search-backend-module-`). You can also find the available search engines and collator/decorator modules documentation in the Alpha API reports: - -**Search engine modules** - -- Postgres [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-pg/alpha-api-report.md); -- Elasticsearch [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-elasticsearch/alpha-api-report.md). - -**Search collator/decorator modules** - -- Catalog [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-catalog/alpha-api-report.md); -- Explore [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-explore/alpha-api-report.md); -- TechDocs [module](https://github.com/backstage/backstage/blob/d7f955f300893f50c4882ea8f5c09aa42dfaacfd/plugins/search-backend-module-techdocs/alpha-api-report.md). diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index 86b6244fb6..f768a9bde8 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -4,34 +4,31 @@ title: Search Engines description: Choosing and configuring your search engine for Backstage --- -Backstage supports 3 search engines by default, an in-memory engine called Lunr, -Elasticsearch and Postgres. You can configure your own search engines by -implementing the provided interface as mentioned in the -[search backend documentation.](./getting-started.md#Backend) - -Provided search engine implementations have their own way of constructing -queries, which may be something you want to modify. Alterations to the querying -logic of a search engine can be made by providing your own implementation of a -QueryTranslator interface. This modification can be done without touching -provided search engines by using the exposed setter to set the modified query -translator into the instance. - -```typescript -const searchEngine = new LunrSearchEngine({ logger: env.logger }); -searchEngine.setTranslator(new MyNewAndBetterQueryTranslator()); -``` +Backstage supports 3 search engines by default, an in-memory engine called [Lunr](#lunr), [Postgres](#postgres) +and [Elasticsearch](#elasticsearch). ## Lunr -Lunr search engine is enabled by default for your backstage instance if you have -not done additional changes to the scaffolded app. +Lunr search engine is enabled by default for your Backstage instance if you have not done additional changes to the scaffolded app. -Lunr can be instantiated like this: +As Lunr is built into the Search backend plugin it can be added like this: -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = new LunrSearchEngine({ logger: env.logger }); -const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend +``` + +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend/alpha')); +/* highlight-add-end */ + +backend.start(); ``` :::note Note @@ -45,34 +42,39 @@ other search engines instead. ## Postgres -The Postgres based search engine only requires that postgres being configured as +The Postgres based search engine only requires that Postgres being configured as the database engine for Backstage. Therefore it targets setups that want to avoid maintaining another external service like Elasticsearch. The search provides decent results and performs well with ten thousands of indexed -documents. The connection to postgres is established via the database manager +documents. The connection to Postgres is established via the database manager also used by other plugins. > **Important**: The search plugin requires at least Postgres 12! -To use the `PgSearchEngine`, make sure that you have a Postgres database -configured and make the following changes to your backend: +First we need to add the plugin: -1. Add a dependency on `@backstage/plugin-search-backend-module-pg` to your - backend's `package.json`. -2. Initialize the search engine. It is recommended to initialize it with a - fallback to the lunr search engine if you are running Backstage for - development locally with SQLite: - -```typescript -// In packages/backend/src/plugins/search.ts - -// Initialize a connection to a search engine. -const searchEngine = (await PgSearchEngine.supported(env.database)) - ? await PgSearchEngine.fromConfig(env.config, { database: env.database }) - : new LunrSearchEngine({ logger: env.logger }); +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-pg ``` -## Optional Configuration +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); +/* highlight-add-end */ + +backend.start(); +``` + +### Optional Configuration The following is an example of the optional configuration that can be applied when using Postgres as the search backend. Currently this is mostly for just the highlight feature: @@ -106,19 +108,32 @@ Backstage supports Elasticsearch (and OpenSearch) search engine connections, indexing and querying out of the box. Available configuration options enable usage of either AWS or Elastic.co hosted solutions, or a custom self-hosted solution. -Similarly to Lunr above, Elasticsearch can be set up like this: +Similarly to Postgres above, Elasticsearch can be set up as follows. -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = await ElasticSearchSearchEngine.fromConfig({ - logger: env.logger, - config: env.config, -}); -const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); +First we need to add the plugin: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend-module-elasticsearch ``` -For the engine to be available, your backend package needs a dependency on -package `@backstage/plugin-search-backend-module-elasticsearch`. +Then add the following line: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +/* highlight-add-start */ +backend.add( + import('@backstage/plugin-search-backend-module-elasticsearch/alpha'), +); +/* highlight-add-end */ + +backend.start(); +``` Elasticsearch needs some additional configuration before it is ready to use within your instance. The configuration options are documented in the @@ -126,67 +141,12 @@ within your instance. The configuration options are documented in the The underlying functionality uses either the official Elasticsearch client version 7.x (meaning that Elasticsearch version 7 is the only one confirmed to -be supported), or the OpenSearch client, when the `aws` or `opensearch `provider +be supported), or the OpenSearch client, when the `aws` or `opensearch` provider is configured. -Should you need to create your own bespoke search experiences that require more -than just a query translator (such as faceted search or Relay pagination), you -can access the configuration of the search engine in order to create new -Elasticsearch clients. The version of the client need not be the same as one -used internally by the Elasticsearch engine plugin. For example: +### Example configurations -```typescript -import { isOpenSearchCompatible } from '@backstage/plugin-search-backend-module-elasticsearch'; -import { Client as ElasticClient } from '@elastic/elasticsearch'; -import { Client as OpenSearchClient } from '@opensearch-project/opensearch'; - -// Return an Elasticsearch client -const esClient = searchEngine.newClient(options => { - if (!isOpenSearchCompatible(options)) { - return new ElasticClient(options); - } - - throw new Error('Incompatible options'); -}); - -// Return an OpenSearch client -const osClient = searchEngine.newClient(options => { - if (isOpenSearchCompatible(options)) { - return new OpenSearchClient(options); - } - - throw new Error('Incompatible options'); -}); -``` - -#### Set custom index template - -The Elasticsearch engine gives you the ability to set a custom index template if needed. - -> Index templates define settings, mappings, and aliases that can be applied automatically to new indices. - -```typescript -// app/backend/src/plugins/search.ts -const searchEngine = await ElasticSearchSearchEngine.initialize({ - logger: env.logger, - config: env.config, -}); - -searchEngine.setIndexTemplate({ - name: '', - body: { - index_patterns: [''], - template: { - mappings: {}, - settings: {}, - }, - }, -}); -``` - -## Example configurations - -### AWS +#### AWS Using AWS hosted Elasticsearch the only configuration option needed is the URL to the Elasticsearch service. The implementation assumes that environment @@ -201,7 +161,7 @@ search: node: https://my-backstage-search-asdfqwerty.eu-west-1.es.amazonaws.com ``` -### Elastic.co +#### Elastic.co Elastic Cloud hosted Elasticsearch uses a Cloud ID to determine the instance of hosted Elasticsearch to connect to. Additionally, username and password needs to @@ -218,7 +178,7 @@ search: password: changeme ``` -### OpenSearch +#### OpenSearch OpenSearch can be self hosted for example with the [official docker image](https://hub.docker.com/r/opensearchproject/opensearch). The configuration requires only the node and authentication. @@ -232,7 +192,7 @@ search: password: changeme ``` -### Others +#### Others Other Elasticsearch instances can be connected to by using standard Elasticsearch authentication methods and exposed URL, provided that the cluster @@ -242,8 +202,6 @@ username/password or an API key. For more information how to create an API key, see [Elastic documentation on API keys](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html). -#### Configuration examples - ##### With username and password ```yaml @@ -273,8 +231,6 @@ you may get an error caused by limited `thread_pool` configuration. ( `429 Too M In this case you need to decrease the batch size to index the resources to prevent this kind of error. You can easily decrease or increase the batch size in your `app-config.yaml` using the `batchSize` option provided for Elasticsearch configuration. -#### Configuration example - **Set batch size to 100** ```yaml diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index 51acd44c1d..f9d20d03c2 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -597,7 +597,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; /* highlight-add-next-line */ import { FoobarEntitiesProcessor } from './providers'; diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 98fae5a95e..4d5b283c4e 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -1031,3 +1031,292 @@ backend.add(catalogModuleCustomDataParser); backend.start(); ``` + +## Incremental Entity Provider + +For large data sources that may not fit into memory but support pagination, the Incremental Entity Provider offers an efficient way to ingest data incrementally, handling deletions and updates seamlessly while minimizing memory usage. + +You can find more details about [why it was created](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#why-did-we-create-it) and its [requirements](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#requirements). + +### Installation + +1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-incremental-ingestion` from the Backstage root directory. + +2. Add the following code to the `packages/backend/src/index.ts` file: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +/* highlight-add-start */ +backend.add( + import( + '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha' + ), +); +/* highlight-add-end */ + +backend.start(); +``` + +### Writing an Incremental Entity Provider + +To create an Incremental Entity Provider, you need to know how to retrieve a single page of data from an API with pagination. The `IncrementalEntityProvider` facilitates this by requiring: + +- **getProviderName:** A unique name to avoid conflicts with other providers. +- **next:** Fetches a specific page of entities, moving the cursor forward. +- **around:** Handles setup and tear-down, wrapping the process that iterates through multiple pages. + +For more information on compatibility, refer to the [requirements](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#requirements). + +In this tutorial, we'll implement an Incremental Entity Provider that interacts with an imaginary API to fetch a list of imaginary services. + +```ts +interface MyApiClient { + getServices(page: number): MyPaginatedResults; +} + +interface MyPaginatedResults { + items: T[]; + totalPages: number; +} + +interface Service { + name: string; +} +``` + +These are the only 3 methods that you need to implement. `getProviderName()` is pretty self explanatory and it's identical to the `getProviderName()` method on a regular Entity Provider. + +```ts +import { IncrementalEntityProvider } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; + +// This will include your pagination information, let's say our API accepts a `page` parameter. +// In this case, the cursor will include `page` +interface Cursor { + page: number; +} + +// This interface describes the type of data that will be passed to your burst function. +interface Context { + apiClient: MyApiClient; +} + +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + getProviderName() { + return `MyIncrementalEntityProvider`; + } +} +``` + +`around` method is used for setup and tear-down. For example, if you need to create a client that will connect to the API, you would do that here. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(); + + await burst({ apiClient }); + + // If you need to do any teardown, you can do it here. + } +} +``` + +If you need to pass a token to your API, then you can create a constructor that will receive a token and use the token to setup the client. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + private readonly token: string; + + constructor(token: string) { + this.token = token; + } + + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(this.token); + + await burst({ apiClient }); + } +} +``` + +The last step is to implement the actual `next` method that will accept the cursor, call the API, process the result and return the result. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + private readonly token: string; + + constructor(token: string) { + this.token = token; + } + + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(this.token); + + await burst({ apiClient }); + } + + async next( + context: Context, + cursor?: Cursor = { page: 1 }, + ): Promise> { + const { apiClient } = context; + + // call your API with the current cursor + const data = await apiClient.getServices(cursor); + + // calculate the next page + const nextPage = page + 1; + + // figure out if there are any more pages to fetch + const done = nextPage > data.totalPages; + + // convert returned items into entities + const entities = data.items.map(item => ({ + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: item.name, + annotations: { + // You need to define these, otherwise they'll fail validation + [ANNOTATION_LOCATION]: this.getProviderName(), + [ANNOTATION_ORIGIN_LOCATION]: this.getProviderName(), + }, + }, + spec: { + type: 'service', + lifecycle: 'production', // Ideally your source has this information + owner: 'unknown', // Ideally your source has this information + }, + }, + })); + + // create the next cursor + const nextCursor = { + page: nextPage, + }; + + return { + done, + entities, + cursor: nextCursor, + }; + } +} +``` + +Now that you have your new Incremental Entity Provider, we can connect it to the catalog. + +### Installing the Incremental Entity Provider + +We'll assume you followed the [Installation](#installation) instructions. Now create a module inside `packages/backend/src/extensions/catalogCustomIncrementalIngestion.ts`. + +```ts title="packages/backend/src/extensions/catalogCustomIncrementalIngestion.ts" +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { incrementalIngestionProvidersExtensionPoint } from '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha'; + +export const catalogModuleCustomIncrementalIngestionProvider = + createBackendModule({ + pluginId: 'catalog', + moduleId: 'custom-incremental-ingestion-provider', + register(env) { + env.registerInit({ + deps: { + incrementalBuilder: incrementalIngestionProvidersExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ incrementalBuilder, config }) { + // Assuming the token for the API comes from config + const token = config.getString('myApiClient.token'); + const myEntityProvider = new MyIncrementalEntityProvider(token); + + const options = { + // How long should it attempt to read pages from the API in a + // single burst? Keep this short. The Incremental Entity Provider + // will attempt to read as many pages as it can in this time + burstLength: { seconds: 3 }, + + // How long should it wait between bursts? + burstInterval: { seconds: 3 }, + + // How long should it rest before re-ingesting again? + restLength: { day: 1 }, + + // Optional back-off configuration - how long should it wait to retry + // in the event of an error? + backoff: [ + { seconds: 5 }, + { seconds: 30 }, + { minutes: 10 }, + { hours: 3 }, + ], + + // Optional. Use this to prevent removal of entities above a given + // percentage. This can be helpful if a data source is flaky and + // sometimes returns a successful status, but fewer than expected + // assets to add or maintain in the catalog. + rejectRemovalsAbovePercentage: 5, + + // Optional. Similar to rejectRemovalsAbovePercentage, except it + // applies to complete, 100% failure of a data source. If true, + // a data source that returns a successful status but does not + // provide any assets to turn into entities will have its empty + // data set rejected. + rejectEmptySourceCollections: true, + }; + + incrementalBuilder.addProvider({ + provider: myEntityProvider, + options, + }); + }, + }); + }, + }); +``` + +Add the module to `packages/backend/src/index.ts` + +```ts title="packages/backend/src/index.ts" +/* highlight-add-next-line */ +import { catalogModuleCustomIncrementalIngestionProvider } from './extensions/catalogCustomIncrementalIngestion'; + +const backend = createBackend(); + +backend.add( + import( + '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha' + ), +); + +/* highlight-add-next-line */ +backend.add(catalogModuleCustomIncrementalIngestionProvider); + +backend.start(); +``` + +For a deep dive into the technical details of the Incremental Entity Provider, see [the README](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion). diff --git a/docs/features/software-catalog/system-model.md b/docs/features/software-catalog/system-model.md index ee7270a960..b93dde37d2 100644 --- a/docs/features/software-catalog/system-model.md +++ b/docs/features/software-catalog/system-model.md @@ -50,7 +50,7 @@ components need to be in a known machine-readable format so we can build further tooling and analysis on top. APIs have a visibility: they are either public (making them available for any -other component to consume), restricted (only available to an allowlisted set of +other component to consume), restricted (only available to an allowed set of consumers), or private (only available within their system). As public APIs are going to be the primary way interaction between components, Backstage supports documenting, indexing and searching all APIs so we can browse them as diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md index dd1228290f..ef90013e49 100644 --- a/docs/features/software-templates/input-examples.md +++ b/docs/features/software-templates/input-examples.md @@ -297,7 +297,7 @@ spec: type: string steps: - - $yaml: https://github.com//example/path/to/action.yaml + - $yaml: https://github.com/example/path/to/action.yaml - id: fetch name: Fetch template diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index d53b8bdf2f..feb0f68048 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -95,7 +95,7 @@ import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-rea import { ValidateKebabCase, validateKebabCaseValidation, -} from './ValidateKebabCase/ValidateKebabCaseExtension'; +} from './ValidateKebabCaseExtension'; export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index 8c2348972c..05f184e90f 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -203,7 +203,7 @@ techdocs: # (Optional) The time (in milliseconds) that the TechDocs backend will wait # for a cache service to respond before continuing on as though the cached - # object was not found (e.g. when the cache sercice is unavailable). The + # object was not found (e.g. when the cache service is unavailable). The # default value is 1000 readTimeout: 500 ``` diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 3a7cfd97bb..e47af8d1d7 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -580,6 +580,10 @@ Note: To refer external diagram files, we need to include the diagrams directory ## How to add Mermaid support in TechDocs +There are two options for adding Mermaid support in TechDocs: using [Kroki](https://kroki.io) or by using [markdown-inline-mermaid](https://github.com/johanneswuerbach/markdown-inline-mermaid). We currently use `markdown-inline-mermaid` for the [Mermaid example on the Demo site](https://demo.backstage.io/docs/default/component/backstage-demo/examples/mermaid/). + +### Using Kroki + To add `Mermaid` support in TechDocs, you can use [`kroki`](https://kroki.io) that creates diagrams from Textual descriptions. It is a single rendering gateway for all popular diagrams-as-a-code tools. It supports an enormous number @@ -678,6 +682,30 @@ Done! Now you have a support of the following diagrams along with mermaid: - `Vega-Lite` - `WaveDrom` +### Using `markdown-inline-mermaid` + +To use `markdown-inline-mermaid` to generate your Mermaid diagrams in TechDocs you'll need to do the following: + +1. In your Dockerfile you will need to make sure you install `markdown-inline-mermaid` like this: `RUN pip3 install mkdocs-techdocs-core markdown-inline-mermaid` +2. You will also need to install the `@mermaid-js/mermaid-cli`, to do that add this: `RUN yarn global add @mermaid-js/mermaid-cli` +3. Now in your `mkdocs.yml` file you will need to add the following section (this is at the root level like `plugins` which you should already have): + + ```yaml title="mkdocs.yml" + markdown_extensions: + - markdown_inline_mermaid + ``` + +4. With this in place you can now add Mermaid diagrams in your Markdown files like this: + + ````md + ```mermaid + sequenceDiagram + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! + ``` + ```` + ## How to implement a hybrid build strategy One limitation of the [Recommended deployment](./architecture.md#recommended-deployment) is that @@ -841,3 +869,8 @@ metadata: annotations: backstage.io/techdocs-entity: system:default/example ``` + +## How to resolve broken links from moved or renamed pages in your documentation site + +TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement. +TechDocs will notify the user that the page they are trying to access is no longer maintained. Then, they will be redirected. External site redirects are not supported. If an external redirect is provided, the user will instead be redirected to the index page of the documentation site. diff --git a/docs/frontend-system/architecture/01-index.md b/docs/frontend-system/architecture/00-index.md similarity index 100% rename from docs/frontend-system/architecture/01-index.md rename to docs/frontend-system/architecture/00-index.md diff --git a/docs/frontend-system/architecture/04-plugins.md b/docs/frontend-system/architecture/04-plugins.md deleted file mode 100644 index 042d4fbc36..0000000000 --- a/docs/frontend-system/architecture/04-plugins.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -id: plugins -title: Frontend Plugins -sidebar_label: Plugins -# prettier-ignore -description: Frontend plugins ---- - -> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** - -## Introduction - - - -## Creating a Plugin - - - -```ts -export const myPlugin = createPlugin({ - id: 'my-plugin', -}); -``` - - - -### Plugin ID - - - -### Plugin Extensions - - - -### Plugin Routes - - - -### Plugin External Routes - - - -### Plugin Feature Flags - - - -## Installing a Plugin in an App - - diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md deleted file mode 100644 index 41702e6823..0000000000 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -id: extension-overrides -title: Frontend Extension Overrides -sidebar_label: Extension Overrides -# prettier-ignore -description: Frontend extension overrides ---- - -> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** - -## Introduction - -An extension override is a building block of the frontend system that allows you to programmatically override app or plugin extensions anywhere in your application. Since the entire application is built mostly out of extensions from the bottom up, this is a powerful feature. You can use it for example to provide your own app root layout, to replace the implementation of a Utility API with a custom one, to override how the catalog page renders itself, and much more. - -In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible, and only use extension overrides when it's necessary to entirely replace the extension. Check the respective extension documentation for guidance. - -## Override App Extensions - -In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. - -### Example - -In the example below, we create a file that exports custom extensions for the app's `light` and `dark` themes: - -```tsx title="packages/app/src/themes.tsx" -import { - createThemeExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; -import { apertureThemes } from './themes'; -import { ApertureLightIcon, ApertureDarkIcon } from './icons'; - -// Creating a light theme extension -const apertureLightTheme = createThemeExtension({ - // highlight-start - namespace: 'app', - name: 'light', - // highlight-end - title: 'Aperture Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), -}); - -// Creating a dark theme extension -const apertureDarkTheme = createThemeExtension({ - // highlight-start - namespace: 'app', - name: 'dark', - // highlight-end - title: 'Aperture Dark Theme', - variant: 'dark', - icon: , - Provider: ({ children }) => ( - - ), -}); - -// Creating an extension overrides preset -export default createExtensionOverrides({ - extensions: [apertureLightTheme, apertureDarkTheme], -}); -``` - -Note that we declare `namespace` as `'app'` while creating the themes, so the system knows we are overriding app extensions. Additionally, to specifically override the `light` and `dark` theme extensions, we set the `name` option to `light` and `dark`. Therefore, to override app theme extensions, we ensure that the extension `namespace` and `name` match those of the default app theme extension definitions. - -Now we are able to use the overrides in a Backstage app: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; -import themeOverrides from './themes'; - -const app = createApp({ - // highlight-next-line - features: [themeOverrides], -}); - -export default app.createRoot(). -``` - -If the plugin you want to change is internal to your company or you just want to replace one of the application's core extensions, you can decide to store the overrides code directly in the app package or extract them to a separate package. - -Note that it can still be a good idea to split your overrides out into separate packages in large projects. But it's up to you to decide how to group the extensions into extension overrides. - -## Override Plugin Extensions - -To override an extension that is provided by a plugin, you need to provide a new extension that has the same ID as the existing extension. That is, all kind, namespace, and name options must match the extension you want to replace. This means that you typically need to provide an explicit `namespace` when overriding extensions from a plugin. - -:::info -We recommend that plugin developers share the extension IDs in their plugin documentation, but usually you can infer the ID by following the [naming patterns](./08-naming-patterns.md) documentation. -::: - -### Example - -Imagine you have a plugin with the ID `'search'`, and the plugin provides a page extension that you want to fully override with your own custom component. To do so, you need to create your page extension with an explicit `namespace` option that matches that of the plugin that you want to override, in this case `'search'`. If the existing extension also has an explicit `name` you'd need to set the `name` of your override extension to the same value as well. - -```tsx title="packages/app/src/search.tsx" -import { - createPageExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; - -// Creating a custom search page extension -const customSearchPage = createPageExtension({ - // highlight-next-line - namespace: 'search', - // Omitting name since it is the index plugin page - defaultPath: '/search', - loader: () => import('./SearchPage').then(m => m.), -}); - -export default createExtensionOverrides({ - extensions: [customSearchPage] -}); -``` - -Don't forget to configure your overrides in the `createApp` function: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; -import searchOverrides from './search'; - -const app = createApp({ - // highlight-next-line - features: [searchOverrides], -}); - -export default app.createRoot(); -``` - -Now let's talk about the last override case, orphan extensions. - -## Create Standalone Extensions - -Sometimes you just need to quickly create a new extension and not overwrite an app extension or plugin. You can also use overrides to create extensions, but remember that if you want to make this extension available for installation by other users, we recommend providing it via a plugin in a separate package. - -### Example - -Imagine you want to create a page that is currently only used by your application, like an Institutional page, for example. You can use overrides to extend the Backstage app to render it. To do so, simply create a page extension and pass it to the app as an override: - -```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; -import { - createPageExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; - -const app = createApp({ - features: [ - createExtensionOverrides({ - extensions: [ - // highlight-start - createPageExtension({ - name: 'institutional', - defaultPath: '/institutional', - loader: () => - import('./institutional').then(m => ), - }), - // highlight-end - ], - }), - ], -}); - -export default app.createRoot(); -``` - -Note that we are omitting `namespace` when creating the page extension. When we omit `namespace`, we are telling the system the new extension is standalone and not an application or plugin extension! diff --git a/docs/frontend-system/architecture/02-app.md b/docs/frontend-system/architecture/10-app.md similarity index 93% rename from docs/frontend-system/architecture/02-app.md rename to docs/frontend-system/architecture/10-app.md index 3b839d4232..d1c3820f09 100644 --- a/docs/frontend-system/architecture/02-app.md +++ b/docs/frontend-system/architecture/10-app.md @@ -16,7 +16,7 @@ Below is a simple example of how to create and render an app instance: ```ts import ReactDOM from 'react-dom/client'; -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; // Create your app instance const app = createApp({ @@ -33,13 +33,13 @@ const rootEl = document.getElementById('root')!; ReactDOM.createRoot(rootEl).render(app); ``` -We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./03-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./06-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`. +We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./20-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./33-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`. -It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./03-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and passes along data to its parent. The following diagram illustrates the shape of a small app extension tree. +It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./20-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and passes along data to its parent. The following diagram illustrates the shape of a small app extension tree. ![frontend system app structure diagram](../../assets/frontend-system/architecture-app.drawio.svg) -Each node in this tree is an extension with a parent node and children. The colored shapes represent extension data inputs and output, where each color is one unique type of data. You can see that there are both extensions that output data that is ignored by the parent, as well as extensions that accept inputs but do not have any children. There are a couple of different tools at your disposal when creating and extension that lets you define different requirements for your inputs and output, which we will cover in greater details in the [Extensions](./03-extensions.md) section. +Each node in this tree is an extension with a parent node and children. The colored shapes represent extension data inputs and output, where each color is one unique type of data. You can see that there are both extensions that output data that is ignored by the parent, as well as extensions that accept inputs but do not have any children. There are a couple of different tools at your disposal when creating and extension that lets you define different requirements for your inputs and output, which we will cover in greater details in the [Extensions](./20-extensions.md) section. A common type of data that is shared between extensions is React elements and components. These can in turn be rendered by each other in their own React components, which ends up forming a parallel tree of React components that is similar in shape to that of the app extension tree. At the top of the app extension tree is a built-in root extension that among other things outputs a React element. This element also ends up being the root of the parallel React tree, and is rendered by the React element returned by `app.createRoot()`. diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md new file mode 100644 index 0000000000..a54573427d --- /dev/null +++ b/docs/frontend-system/architecture/15-plugins.md @@ -0,0 +1,91 @@ +--- +id: plugins +title: Frontend Plugins +sidebar_label: Plugins +# prettier-ignore +description: Frontend plugins +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + +Frontend plugins are a foundational building block in Backstage and the frontend system. They are used to encapsulate and provide functionality for a Backstage app, such as new pages, navigational elements, and APIs; as well as extensions and features for other plugins, such as entity page cards and content for the Software Catalog, or result list items for the search plugin. + +Each plugin is typically shipped in a separate NPM package, whether that's a published package, or just in the local workspace. The plugin instance should always the `default` export of the package, either via the main entry-point or the `/alpha` sub-path export. Each plugin package is limited to exporting a single plugin instance. In a local workspace you could use a different structure if preferred, but this is considered a non-standard layout and should be avoided in published packages. + +## Creating a Plugin + +Frontend plugin instances are created with the `createFrontendPlugin` function, which is provided by the `@backstage/frontend-plugin-api` package. It takes a single options object that provides all of the necessary configuration for the plugin. In particular you will want to provide [extensions](./20-extensions.md) for your plugin, as that is the way that you can provide new functionality to the app. + +```tsx +// This creates a new extension, see "Extension Blueprints" documentation for more details +const myPage = PageBlueprint.make({ + params: { + defaultPath: '/my-page', + loader: () => import('./MyPage').then(m => ), + }, +}); + +export default createFrontendPlugin({ + id: 'my-plugin', + extensions: [myPage], +}); +``` + +### `pluginId` option + +Each plugin needs an ID, which is used to uniquely identify the plugin within an entire Backstage system. The ID does not have to be globally unique across all of the NPM ecosystem, although you generally want to strive for that. It is not possible to install multiple plugins with the same ID in a single Backstage app. + +The plugin ID should generally be part of the of the package name and use kebab-case. See both the [frontend naming patterns section](./50-naming-patterns.md), as well as the [package metadata section](../../tooling/package-metadata.md#name) for more information. + +### `extensions` option + +These are the [extensions](./20-extensions.md) that the plugin provides to the app. Note that you should not export any of these extensions separately from the plugin package, as they can already by accessed via the `getExtension` method of the plugin instance using the extension ID. + +The extensions that you provide to a plugin will have their `namespace` set to the plugin ID by default. For example, if you create an extension using the `PageBlueprint` without any particular naming options and install that via a plugin with the ID `my-plugin`, the final extension ID will be `page:my-plugin`. You can read more about how this works in the [extension structure documentation](./20-extensions.md#extension-structure). + +### `routes` and `externalRoutes` options + +These are the routes that the plugin exposes to the app. The `routes` option declares all of the target routes that your plugin provides, i.e. routes that other plugins link to. The `externalRoutes` option instead declares all the outgoing routes, i.e. routes that your plugins links to, which you can bind to the `routes` of other plugins. See the [routes documentation](./36-routes.md) for more information how to set up cross-plugin navigation. + +### `featureFlags` option + +This is a list of feature flag declarations that your plugin provides to the app. This makes sure that the feature flags are correctly registered and can be toggled in the app. To read a feature flag you can use the feature flags [Utility API](../architecture/33-utility-apis.md), accessible via `featureFlagsApiRef`. + +## Installing a Plugin in an App + +A plugin instance is considered a frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. + +## Overriding a Plugin + +A plugin might not always behave exactly the way you want. It could be that you want to remove particular extensions, decorate them a bit, replace them with your own, or simply add new ones. Regardless of your exact use-case, you can use the `plugin.withOverrides` method to create a new copy of the plugin with the desired changes. When doing so you can also access the original extensions provided by the plugin, and use the [extension override](./25-extension-overrides.md) API to make changes to individual extensions: + +```tsx +import plugin from '@backstage/plugin-catalog'; + +export default plugin.withOverrides({ + // These overrides are merged with the original extensions + extensions: [ + // Override the catalog nav item to use a custom icon + plugin.getExtension('nav-item:catalog').override({ + factory: origFactory => [ + NavItemBlueprint.dataRefs.target({ + ...origFactory().get(NavItemBlueprint.dataRefs.target), + icon: CustomCatalogIcon, + }), + ], + }), + // Override the catalog index page with a completely custom implementation + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: plugin.routes.catalogIndex, + loader: () => import('./CustomCatalogIndexPage').then(m => ), + }, + }), + ], +}); +``` + +You can keep the plugin override in your app package, but it can often be a good idea to separate it out into its own package, especially if the overrides are complex or you want distinct ownership of the override. For example, if you are overriding the `@backstage/plugin-catalog` plugin, you might create a new package called `@internal/plugin-catalog` at `plugins/catalog` in your workspace, which exports the overridden plugin instance. diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/20-extensions.md similarity index 66% rename from docs/frontend-system/architecture/03-extensions.md rename to docs/frontend-system/architecture/20-extensions.md index 2f3daba6f8..02f4ba9d05 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -8,7 +8,7 @@ description: Frontend extensions > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -As mentioned in the [previous section](./02-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns. +As mentioned in the [previous section](./10-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns. ## Extension Structure @@ -18,12 +18,12 @@ Each extensions has a number of different properties that define how it behaves ### ID - - The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics. +When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. + +The extension ID will be constructed using the pattern `[:][][/][]`, where the separating `/` is only present if both a namespace and name are defined. + ### Output The output of an extension is the data that it provides to its parent extension, and ultimately its contribution to the app. The output itself comes in the form of a collection of arbitrary values, anything that can be represented as a TypeScript type. However, each individual output value must be associated with a shared reference known as an extension data reference. You must also use these same references to be able to access individual output values of an extension. @@ -32,7 +32,7 @@ The output of an extension is the data that it provides to its parent extension, The inputs of an extension define the data that it received from its children. Each extension can have multiple different inputs identified by an input name. These inputs each have their own set of data that they expect, which is defined as a collection of extension data references. An extension will only have access to the data that it has explicitly requested from each input. -### Attachment Point +### Attachment point The attachment point of an extension decides where in the app extension tree it will be located. It is defined by the ID of the parent extension, as well as the name of the input to attach to. Through the attachment point the extension will share its own output as inputs to the parent extension. An extension can only be attached to an input that matches its own output, it is an error to try to attach an extension to an input the requires data that the extension does not provide in its output. @@ -44,7 +44,7 @@ Each extension in the app can be disabled, meaning it will not be instantiated a The ordering of extensions is sometimes very important, as it may for example affect in which order they show up in the UI. When an extension is toggled from disabled to enabled through configuration it resets the ordering of the extension, pushing it to the end of the list. It is generally recommended to leave extensions as disabled by default if their order is important, allowing for the order in which their are enabled in the configuration to determine their order in the app. -### Configuration & Configuration Schema +### Configuration & configuration schema Each extension can define a configuration schema that describes the configuration that it accepts. This schema is used to validate the configuration provided by integrators, but also to fill in default configuration values. The configuration itself is provided by integrators in order to customize the extension. It is not possible to provide a default configuration of an extension, this must instead be done through defaults in the configuration schema. This allows for a simpler configuration logic where multiple configurations of the same extension completely replace each other rather than being merged. @@ -54,7 +54,7 @@ The extension factory is the implementation of the extension itself. It is a fun Extension factories should be lean and not do any heavy lifting or async work, as they are called during the initialization of the app. For example, if you need to do an expensive computation to generate your output, then prefer outputting a callback that does the computation instead. This allows the parent extension to defer the computation for later so that you avoid blocking the app startup. -## Creating an Extensions +## Creating an extension Extensions are created using the `createExtension` function from `@backstage/frontend-plugin-api`. At minimum you need to provide an ID, attachment point, output definition, and a factory function. The following example shows the creation of a minimal extension: @@ -64,28 +64,22 @@ const extension = createExtension({ // This is the attachment point, `id` is the ID of the parent extension, // while `input` is the name of the input to attach to. attachTo: { id: 'my-parent', input: 'content' }, - // The output map defines the outputs of the extension. The object keys - // are only used internally to map the outputs of the factory and do - // not need to match the keys of the input. - output: { - element: coreExtensionData.reactElement, - }, + // The output option defines the allowed and required outputs of the extension factory. + output: [coreExtensionData.reactElement], // This factory is called to instantiate the extensions and produce its output. factory() { - return { - element:
Hello World
, - }; + return [coreExtensionData.reactElement(
Hello World
)]; }, }); ``` -Note that while the `createExtension` is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many extension creator functions exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. +Note that while the `createExtension` function is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many [extension blueprints](./23-extension-blueprints.md) exported by both the core APIs and plugins that make it easier to create extensions for more specific usages. -## Extension Data +## Extension data Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extensions. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. -### Extension Data References +### Extension data references To create a new extension data reference to represent a type of shared extension data you use the `createExtensionDataRef` function. When defining a new reference you need to provide an ID and a TypeScript type, for example: @@ -101,59 +95,53 @@ The `ExtensionDataRef` can then be used to describe an output property of the ex ```tsx const extension = createExtension({ // ... - output: { - element: reactElementExtensionDataRef, - }, + output: [reactElementExtensionDataRef], factory() { - return { - element:
Hello World
, - }; + return [reactElementExtensionDataRef(
Hello World
)]; }, }); ``` -### Extension Data Uniqueness +### Extension data uniqueness -Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case [`core.reactElement`](https://github.com/backstage/backstage/blob/916da47e8abdb880877daa18881eb8fdbb33e70a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts#L23). This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. +Note that you are **not** allowed to repeat the same data reference in the outputs, or return multiple values for the same reference. Multiple outputs for the same reference will conflict with each other and cause an error. If you want to output multiple values of the same TypeScript type you should create separate references for each value. That in turn means that overly generic extension data references are a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. ```tsx const extension = createExtension({ // ... - output: { - // ❌ Bad example - outputting values of same type - element1: reactElementExtensionDataRef, - element2: reactElementExtensionDataRef, - }, + output: [ + // ❌ Bad example - duplicate output declaration + reactElementExtensionDataRef, + reactElementExtensionDataRef, + ], factory() { - return { - element1:
Hello World
, - element2:
Hello World
, - }; + return [ + // ❌ Bad example - duplicate output values + reactElementExtensionDataRef(
Hello
), + reactElementExtensionDataRef(
World
), + ]; }, }); ``` -### Core Extension Data +### Core extension data We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. For a full list and explanations of all types of core extension data, see the [core extension data reference](../building-plugins/04-built-in-data-refs.md). -### Optional Extension Data +### Optional extension data By default all extension data is required, meaning that the extension factory must provide a value for each output. However, it is possible to make extension data optional by calling the `.optional()` method. This makes it optional for the factory function to return a value as part of its output. When calling the `.optional()` method you create a new copy of the extension data reference, it does not mutate the existing reference. ```tsx const extension = createExtension({ // ... - output: { - element: coreExtensionData.reactElement.optional(), - }, + output: [coreExtensionData.reactElement.optional()], factory() { - return { - element: - Math.random() < 0.5 ? : undefined, - }; + return Math.random() > 0.5 + ? [coreExtensionData.reactElement(
Hello World
)] + : []; }, }); ``` @@ -167,17 +155,19 @@ const navigationExtension = createExtension({ // ... inputs: { // [1]: Input - logo: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true, optional: true }, - ), + logo: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), }, factory({ inputs }) { return { element: ( - + ), }; }, @@ -193,10 +183,9 @@ So how can we now attach the output to the parent extension's input? If we think const navigationItemExtension = createExtension({ // ... attachTo: { id: 'app/nav', input: 'items' }, + output: [coreExtensionData.reactElement], factory() { - return { - element: Home, - }; + return [coreExtensionData.reactElement(Home)]; }, }); @@ -205,26 +194,22 @@ const navigationExtension = createExtension({ // [2]: Extension `id` will be `app/nav` following the extension naming pattern namespace: 'app', name: 'nav', - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], inputs: { - items: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + items: createExtensionInput([coreExtensionData.reactElement]), }, factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, // ... }); @@ -232,27 +217,27 @@ const navigationExtension = createExtension({ In this case the extension input `items` is an array, where each individual item is an extension that attached itself to the extension inputs of this `id`. -With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. However, it is discouraged to consume the `node` here unless needed. If we are looking at the `factory` function from the example above we could access the `node` like the following: +In addition to being able to access data passed through the input, you also have access to the underlying app `node`. This can be useful if you for example want to get the ID of the attached extension. However, avoid using the `node` unless needed, it is generally better to stick to only consuming the provided data. If we are looking at the `factory` function from the example above we could access the `node` like the following: ```tsx // ... factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( ), - }; + ]; }, ``` -## Extension Configuration +## Extension configuration With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefore we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: @@ -262,20 +247,20 @@ const navigationExtension = createExtension({ namespace: 'app', name: 'nav', // [3]: Extension `id` will be `app/nav` following the extension naming pattern - configSchema: createSchemaFromZod(z => - z.object({ - title: z.string().default('Sidebar Title'), - }), - ), + config: { + schema: { + title: z => z.string().default('Sidebar Title'), + }, + }, factory({ config }) { - return { - element: ( + return [ + coreExtensionData.reactElement( + , ), - }; + ]; }, // ... }); @@ -293,47 +278,40 @@ app: title: 'Backstage' ``` -## Extension Creators +## Extension factory as a generator function -With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. +In all examples so far we have defined the extension factory as a regular function that returns its output in an array. However, the only requirement is that the factory function returns any iterable of extension data values. This means that you can also define the factory function as a generator function, which allows you to yield values one by one. This is particularly useful if you want to conditionally output values. -If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefore `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. +For example, this is how we could define an extension where its output depends on the configuration: ```tsx -export const HomeNavIcon = createNavItemExtension({ - routeRef: routeRefForTheHomePage, - title: 'Home', - icon: HomeIcon, +const exampleExtension = createExtension({ + // ... + config: { + schema: { + disableIcon: z.boolean().default(false), + }, + }, + output: [coreExtensionData.reactElement, iconDataRef.optional()], + *factory({ config }) { + yield coreExtensionData.reactElement(
Hello World
); + + if (!config.disableIcon) { + yield iconDataRef(); + } + }, }); ``` -### Extension Kind - -With the example `HomeNavIcon` will end up on the extension input `items` of the extensions with the id `app/nav`. It raises the question what the `id` of the `HomeNavIcon` itself is. The extension creator for the navigation item has a defined `kind`, which by convention matches the own name. So in this example `createNavItemExtension` sets the kind to `nav-item`. - -The `id` of the extension is then build out of `namespace`, `name` & `kind` like the following - where `namespace` & `name` are optional properties that can be passed to the extension creator: - -``` -id: kind:namespace/name -``` - -For more information on naming of extension refer to the [naming patterns documentation](./08-naming-patterns.md). - -### Extension Creators in libraries - -Extension creators should be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. - -If an extension is only for in-house tweaks, it's okay to put it in the plugin package. But if you want other open source plugins to use it, or you already have a `-react` package, always put extension creators in the `-react` package. - -## Extension Boundary +## Extension boundary The `ExtensionBoundary` wraps extensions with several React contexts for different purposes ### Suspense -All React elements rendered by extension creators should be wrapped in the extension boundary. With `Suspense` the extension can than load resources asynchronously with having a loading fallback. It also allows to lazy load the whole extension similar to how plugins are currently lazy loaded in Backstage. +Most React elements rendered by extensions should be wrapped in the extension boundary. With `Suspense` the extension can than load resources asynchronously with having a loading fallback. It also allows to lazy load the whole extension similar to how plugins are currently lazy loaded in Backstage. -### Error Boundary +### Error boundary Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncaught error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. @@ -341,32 +319,21 @@ Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallbac Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`. Whether an extension is routable is inferred from its outputs, but you can also explicitly control this behavior by passing the `routable` prop to `ExtensionBoundary`. -The `ExtensionBoundary` can be used like the following in an extension creator: +The `ExtensionBoundary` can be used like the following in an extension: ```tsx -export function createSomeExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options): ExtensionDefinition { - return createExtension({ - // ... - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - routeRef: options.routeRef, - element: ( - - - - ), - }; - }, - }); -} +const routableExtension = createExtension({ + // ... + factory({ config, inputs, node }) { + return [ + coreExtensionData.path(config.path), + coreExtensionData.routeRef(myRouteRef), + coreExtensionData.reactElement( + + + , + ), + ]; + }, +}); ``` diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md new file mode 100644 index 0000000000..ee09e7fec6 --- /dev/null +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -0,0 +1,142 @@ +--- +id: extension-blueprints +title: Frontend Extension Blueprints +sidebar_label: Extensions Blueprints +# prettier-ignore +description: Frontend extensions +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +The `createExtension` function and related APIs is considered a low-level building and fairly advanced building block, and is not typically what you would use when building plugins and features. Instead, the core APIs and plugins provide extension blueprints that makes it easier to create extensions for specific usages. These blueprints accept a number of parameters that is up to each blueprint to define, and then creates a new extension using the provided parameters. New blueprints are created using the `createExtensionBlueprint` function, and are by convention exported with the symbol `Blueprint`. If you are curious about what blueprints are available from a plugin or package, look for `*Blueprint` exports in the package's API, for plugins these are typically found in the `*-react` package. + +## Creating an extension from a blueprint + +Every extension blueprint provides a `make` method that can be used to create new extensions. It is a simple way to create a new extension where the base blueprint provides all the necessary functionality. All you need to do is to provide the necessary blueprint parameters, but you also have the ability to provide additional options, for example a `name` for the extension. + +The following is a simple example of how one might use the blueprint `make` method to create a new extension: + +```tsx +const myPageExtension = PageBlueprint.make({ + params: { + defaultPath: '/my-page', + loader: () => import('./components/MyPage').them(m => ), + }, +}); +``` + +The returned `myPageExtension` is an extension which is ready to be used in a plugin. It is the same type of object as is returned by the lower level `createExtension` function. + +## Creating an extension from a blueprint with overrides + +Every extension blueprint also provides a `makeWithOverrides` method. It is useful in cases where you want to provide additional integration points for an extension created with a blueprint. You might for example want to define additional inputs or configuration schema, or use the existing configuration to dynamically compute the parameters passed to the blueprint. + +The following is an example of how one might use the blueprint `makeWithOverrides` method to create a new extension: + +```tsx +const myPageExtension = PageBlueprint.makeWithOverrides({ + config: { + schema: { + layout: z => z.enum(['grid', 'rows']).default('grid'), + }, + }, + // The original blueprint factory is provided as the first argument + factory(originalFactory, { config }) { + // Call and forward the result from the original factory, providing + // the blueprint parameters as the first argument. + return originalFactory({ + defaultPath: '/my-page', + loader: () => + import('./components/MyPage').them(m => ( + // We can now access values from the factory context when providing + // the blueprint parameters, such as config values. + + )), + }); + }, +}); +``` + +When using `makeWithOverrides`, we no longer pass the blueprint parameters directly. Instead, we provide a `factory` function that receives the original blueprint factory as the first argument, and the extension factory context as the second. We can then call the original blueprint factory with the blueprint parameters and forward the result as the return value of out factory. Notice that when passing the blueprint parameters using this pattern we have access to a lot more information than when using the `make` method, at the cost of being more complex. + +Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method. + +## Creating an extension blueprint + +To create a new extension blueprint, you use the `createExtensionBlueprint` function. At the surface it is very similar to `createExtension`, but with a few key differences. Firstly you must provide a `kind` option, which will be the kind of all extensions created with the blueprint. See the [naming patterns section](./50-naming-patterns.md) for more information about how to select a good extension kind. Secondly, the `factory` function has a new signature where the first parameter is the blueprint parameters, and the second is the factory context. And finally, rather than returning an extension, `createExtensionBlueprint` returns a blueprint object with the `make` method and friends, which is used as is described above. + +The following is an example of how one might create a new extension blueprint: + +```tsx +export interface MyWidgetBlueprintParams { + defaultTitle: string; + element: JSX.Element; +} + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + config: { + schema: { + title: z.string().optional(), + }, + }, + output: [coreExtensionData.reactElement], + factory(params: MyWidgetBlueprintParams, { config }) { + return [ + // Note that while this is a valid pattern, you might often want to + // return separate pieces of data instead, more on that below. + coreExtensionData.reactElement( + + {params.element} + , + ), + ]; + }, +}); +``` + +This is of course a quite bare-bones example blueprint, but still a very real example. Blueprints can be very simple, there's already a lot of value in encapsulating the extension kind, attachment point, and output in a blueprint. + +Most of the options provided to `createExtensionBlueprint` can be overridden when using `makeWithOverrides` to create an extension from the blueprint. These overrides work the same way as [extension overrides](./25-extension-overrides.md), and we defer to that documentation for more information on how overrides work. + +### Blueprint-specific extension data references + +In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions. + +To do that, we create a new extension data reference for our widget title. This references is provided via the `dataRefs` options when we create the blueprint, which makes it available for use via `MyWidgetBlueprint.dataRefs.widgetTitle`. + +```tsx +export interface MyWidgetBlueprintParams { + defaultTitle: string; + element: JSX.Element; +} + +const widgetTitleRef = createExtensionDataRef().with({ + id: 'my-plugin.widget.title', +}); + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + config: { + schema: { + title: z.string().optional(), + }, + }, + output: [widgetTitleRef, coreExtensionData.reactElement], + factory(params: MyWidgetBlueprintParams, { config }) { + return [ + widgetTitleRef(config.title ?? params.defaultTitle), + coreExtensionData.reactElement(params.element), + ]; + }, + dataRefs: { + widgetTitle: widgetTitleRef, + }, +}); +``` + +### Extension Blueprints in libraries + +If you are publishing a plugin, the extension creators should always be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md new file mode 100644 index 0000000000..785843f6e0 --- /dev/null +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -0,0 +1,307 @@ +--- +id: extension-overrides +title: Frontend Extension Overrides +sidebar_label: Extension Overrides +# prettier-ignore +description: Frontend extension overrides +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + +An important customization point in the frontend system is the ability to override existing extensions. It can be used for anything from slight tweaks to the extension logic, to completely replacing an extension with a custom implementation. While extensions are encouraged to make themselves configurable, there are many situations where you need to override an extension to achieve the desired behavior. The ability to override extensions should be kept in mind when building plugins, and can be a powerful tool to allow for deeper customizations without the need to re-implement large parts of the plugin. + +In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides. + +## Overriding an extension + +Every extension created with `createExtension` comes with an `override` method, including those created from an [extension blueprint](./23-extension-blueprints.md). The `override` method **creates a new extension**, it does not mutate the existing extension. This new extension in created in such a way that if it is installed adjacent to the existing extension, it will take precedence and override the existing extension. While the `override` method does create new extension instances, it is not intended to be used as a way to create multiple new extensions from a base template, for that use-case you will want to use an [extension blueprint](./23-extension-blueprints.md) instead. + +The following is an example of calling the `.override(...)` method on an extension: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + return originalFactory(); + }, +}); +``` + +This override is a no-op, it does not change the behavior of the extension, but simply forwards the outputs from the original extension factory. If you are familiar with [extension blueprints](./23-extension-blueprints.md), you will recognize this factory override pattern where we get access to the original factory function. In fact the only difference is that we do not need to pass any parameters to the original factory. The first parameter is now instead the optional factory context overrides, more on that as we dive into each override pattern in the following sections. + +## Overriding original factory outputs + +When overriding an extension you can choose to forward the existing outputs, or replace them with your own. The override factory has an exception to the rule that extension factories can only return a single value for each declared output. It will instead always use the **last** value provided for each extension data reference. This makes it possible to forward the outputs from the original factory, but also provide your own, for example: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + return [ + ...originalFactory(), + coreExtensionData.reactElement(

Hello Override

), + ]; + }, +}); +``` + +You can also access individual data values from the original factory, in order to decorate the output: + +```tsx +const myOverrideExtension = myExtension.override({ + factory(originalFactory) { + const originalOutput = originalFactory(); + const originalElement = originalOutput.get(coreExtensionData.reactElement); + + return [ + ...originalOutput, + coreExtensionData.reactElement( +
+ Show original element + {originalElement} +
, + ), + ]; + }, +}); +``` + +Just as [extension factories can be declared as a generator function](./20-extensions.md#extension-factory-as-a-generator-function), so can the override factory. Using a generator function, the first example above can be written as follows: + +```tsx +const myOverrideExtension = myExtension.override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

Hello Override

); + }, +}); +``` + +Note the `yield*` expression, which forwards all values from the provided iterable to the generator, in this case the original factory output. + +## Overriding declared outputs + +When overriding an extension you can provide a new output declaration. This **replaces** any existing output declaration, which means that if you want to forward any of the original output you will need to declare it again. The following example shows how to override an extension and replace the output declaration: + +```tsx +// Original extension +const exampleExtension = createExtension({ + name: 'example', + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(

Example

)], +}); + +// Override extension, with additional outputs +const overrideExtension = exampleExtension.override({ + output: [coreExtensionData.reactElement, coreExtensionData.routePath], + factory(originalFactory) { + return [...originalFactory(), coreExtensionData.routePath('/example')]; + }, +}); +``` + +When overriding the output declaration you don't need to include the original outputs. Just remember that you will no longer be able to directly forward the output from the original factory, and will still need to adhere to the contract of the input that the extension is attached to. + +## Overriding declared inputs + +When overriding an extension you can also provide new input declarations. You can define any number of new inputs, but you are **not** able to override the existing inputs declared by the original extension. The new inputs will be merged with the existing ones, giving the override factory access to both. The following example shows how to override an extension and add a new input declaration: + +```tsx +const myOverrideExtension = myExtension.override({ + inputs: { + myOverrideInput: createExtensionInput([coreExtensionData.reactElement]), + }, + factory(originalFactory, { inputs }) { + const originalOutput = originalFactory(); + const originalElement = originalOutput.get(coreExtensionData.reactElement); + + return [ + ...originalOutput, + coreExtensionData.reactElement( +
+

Original element

+ {originalElement} +

Additional inputs

+
    + {inputs.myOverrideInput.map(i => ( +
  • + {i.get(coreExtensionData.reactElement)} +
  • + ))} +
+
, + ), + ]; + }, +}); +``` + +## Overriding configuration schema + +Overriding the configuration schema works very similarly to overriding the declared inputs. You can define new configuration fields that will be merged with the existing ones, but you can not re-declare existing fields. The following example shows how to override an extension and add a new configuration field: + +```tsx +const exampleExtension = createExtension({ + config: { + schema: { + foo: z => z.string(), + }, + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + config: { + schema: { + bar: z => z.string(), + }, + }, + factory(originalFactory, { config }) { + // + console.log(`foo=${config.foo} bar=${config.bar}`); + return originalFactory(); + }, +}); +``` + +## Overriding original factory config context + +In all examples so far we have called the `originalFactory` callback without any arguments. It is however possible to override parts of the factory context for the original factory using the first parameter of the original factory. This can be useful if you want to override the provided configuration or change the inputs in some way. Note that if you are implementing a `factory` for a blueprint, the override factory context will instead be the second parameter of the original factory function. The following is an example of how to override the configuration for the original factory: + +```tsx +const exampleExtension = createExtension({ + name: 'example', + config: { + schema: { + layout: z => z.enum(['grid', 'list']).optional(), + }, + }, + output: [coreExtensionData.reactElement], + factory: ({ config }) => [ + coreExtensionData.reactElement( + , + ), + ], +}); + +const overrideExtension = exampleExtension.override({ + factory(originalFactory, { config }) { + return originalFactory({ + config: { + // Switch default layout from 'list' to 'grid' + layout: config.layout ?? 'grid', + }, + }); + }, +}); +``` + +As can be seen in the above example we can provide a new configuration object in the `originalFactory` call using the `config` property. When providing the `config` property we will completely override the original configuration object that would otherwise have been provided to the original factory. Note that this object must adhere to the output type of the configuration schema, which might not be intuitive. It's due to the configuration having already been processed and validated by Zod at this point, which means that things like defaults in the schema will not be applied again. + +## Overriding original factory inputs context + +In addition to the configuration, you are also able to override the inputs provided to the original factory. Just like when overriding configuration you will completely replace the original inputs with the new ones, but you are able to forward the inputs that you are receiving to the override factory. + +You can override each input in one of two ways, which can not be combined. You can forward (or not forward) the original input, optionally filtering out individual items or reordering them. Or you can provide new values for the input, which will replace the original input. When providing new values you must forward all existing inputs and the inputs can not be reordered, and when forwarding the existing inputs you can not provide new values. + +The following example shows how to override the values provided for each input item: + +```tsx +const exampleExtension = createExtension({ + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + items: inputs.items.map(i => [ + coreExtensionData.reactElement( + {i.get(coreExtensionData.reactElement)}, + ), + ]), + }, + }); + }, +}); +``` + +In contrast, the following example shows how to forward the original inputs, but in a different order: + +```tsx +const exampleExtension = createExtension({ + inputs: { + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), + items: createExtensionInput([coreExtensionData.reactElement]), + }, + // ... +}); + +const overrideExtension = exampleExtension.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + // We can also skip forwarding the original input, if we want to remove it + content: inputs.content, + // Sort items input by their extension ID + items: inputs.items.toSorted((a, b) => + a.node.spec.id.localeCompare(b.node.spec.id), + ), + }, + }); + }, +}); +``` + +## Installing override extension in an app + +To install extension overrides in a Backstage app you should use `plugin.withOverrides` whenever you are overriding or adding extensions for a plugin. See the section on [overriding a plugin](./15-plugins.md#overriding-a-plugin) for more information. + +There is also a `createExtensionOverrides` function that can be used to install a collection of standalone extensions in an app. This method will be replaced with a different mechanism in the future, but for now remains the only way to override the built-in extensions in the app or to package extensions for a plugin package separate from the plugin itself. + +Note that while using either of these options you don't necessarily need to use the extension `.override(...)` method to create the overrides. You can also create new extensions with `createExtension` or a blueprint that are either completely net-new extensions, or override an existing extension by using the same `kind`, `namespace` and `name` to produce the same extension ID. + +### Creating a standalone extension bundle + +The following example shows how to create a standalone extension bundle that overrides the search page from the search plugin: + +```tsx +import { + createPageExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; + +const customSearchPage = PageBlueprint.make({ + // Since this is a standalone extension we need to provide the namespace to match the search plugin + namespace: 'search', + params: { + defaultPath: '/search', + loader: () => + import('./CustomSearchPage').then(m => ), + }, +}); + +export default createExtensionOverrides({ + extensions: [customSearchPage], +}); +``` + +Assuming the above code resides in the `@internal/search-page` package, you can install it in your app like this: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; +import searchPageOverride from '@internal/search-page'; + +const app = createApp({ + // highlight-next-line + features: [searchPageOverride], +}); + +export default app.createRoot(); +``` diff --git a/docs/frontend-system/architecture/08-references.md b/docs/frontend-system/architecture/30-references.md similarity index 100% rename from docs/frontend-system/architecture/08-references.md rename to docs/frontend-system/architecture/30-references.md diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/33-utility-apis.md similarity index 97% rename from docs/frontend-system/architecture/06-utility-apis.md rename to docs/frontend-system/architecture/33-utility-apis.md index 49e5854b65..c20936f819 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/33-utility-apis.md @@ -10,7 +10,7 @@ description: Utility APIs ## Overview -Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. +Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/20-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. diff --git a/docs/frontend-system/architecture/07-routes.md b/docs/frontend-system/architecture/36-routes.md similarity index 98% rename from docs/frontend-system/architecture/07-routes.md rename to docs/frontend-system/architecture/36-routes.md index 9db4d007b6..2a9b372b1c 100644 --- a/docs/frontend-system/architecture/07-routes.md +++ b/docs/frontend-system/architecture/36-routes.md @@ -42,7 +42,7 @@ The code snippet in the previous section does not indicate which plugin the rout ```tsx title="plugins/catalog/src/plugin.tsx" import React from 'react'; import { - createPlugin, + createFrontendPlugin, createPageExtension, } from '@backstage/frontend-plugin-api'; import { indexRouteRef } from './routes'; @@ -55,7 +55,7 @@ const catalogIndexPage = createPageExtension({ loader: () => import('./components').then(m => ), }); -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog', // highlight-start routes: { @@ -196,7 +196,7 @@ Now the only thing left is to provide the page and external route via a plugin: ```tsx title="plugins/catalog/src/plugin.tsx" import React from 'react'; import { - createPlugin, + createFrontendPlugin, createPageExtension, useRouteRef, } from '@backstage/frontend-plugin-api'; @@ -208,7 +208,7 @@ const catalogIndexPage = createPageExtension({ loader: () => import('./components').then(m => ), }); -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog', routes: { index: indexRouteRef, @@ -256,7 +256,7 @@ app: We also have the ability to express this in code as an option to `createApp`, but you of course only need to use one of these two methods: ```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; import catalog from '@backstage/plugin-catalog'; import scaffolder from '@backstage/plugin-scaffolder'; @@ -404,7 +404,7 @@ Finally, see how a plugin can provide subroutes: ```tsx title="plugins/catalog/src/plugin.tsx" import React from 'react'; import { - createPlugin, + createFrontendPlugin, createPageExtension, } from '@backstage/frontend-plugin-api'; import { indexRouteRef, detailsSubRouteRef } from './routes'; @@ -415,7 +415,7 @@ const catalogIndexPage = createPageExtension({ loader: () => import('./components').then(m => ), }); -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog', routes: { index: indexRouteRef, diff --git a/docs/frontend-system/architecture/08-naming-patterns.md b/docs/frontend-system/architecture/50-naming-patterns.md similarity index 91% rename from docs/frontend-system/architecture/08-naming-patterns.md rename to docs/frontend-system/architecture/50-naming-patterns.md index f031b80d84..11e0b8e733 100644 --- a/docs/frontend-system/architecture/08-naming-patterns.md +++ b/docs/frontend-system/architecture/50-naming-patterns.md @@ -23,7 +23,7 @@ Example: ```ts // This declaration is only for internal usage in tests. This could also be a direct default export. -export const userSettingsPlugin = createPlugin({ +export const userSettingsPlugin = createFrontendPlugin({ id: 'user-settings', ... }) @@ -42,7 +42,7 @@ Note that while we use this naming pattern for the plugin instance this is only | ID | `[:][/]` | `'core.nav'`, `'page:user-settings'`, `'entity-card:catalog/about'` | | Symbol | `[][]` | `coreNav`, `userSettingsPage`, `catalogAboutEntityCard` | -When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the blueprint creator, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. +When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the extension blueprint, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. Example: @@ -67,7 +67,7 @@ const catalogSearchResultListItem = SearchResultListItemBlueprint.make({ }); // Note that the extensions themselves are not exported, only the plugin instance -export const catalogPlugin = createPlugin({ +export const catalogPlugin = createFrontendPlugin({ id: 'catalog', extensions: [catalogEntityPage, catalogSearchResultListItem /* ... */], }); diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md new file mode 100644 index 0000000000..a1ab0197f7 --- /dev/null +++ b/docs/frontend-system/architecture/60-migrations.md @@ -0,0 +1,104 @@ +--- +id: migrations +title: Frontend System Migrations +sidebar_label: Migrations +# prettier-ignore +description: Migration documentation for different versions of the frontend system core APIs. +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + +This section provides migration guides for different versions of the frontend system core APIs. Each guide will provide a summary of the changes that have been made for a particular Backstage release, and how to update your usage of the core APIs. + +This guide is intended for app and plugin authors who have already migrated their code to the new frontend system, and are looking to keep it up to date with the latest changes. These guides do not cover trivial migrations that can be explained in a deprecation message, such as a renamed export. + +## 1.30 + +### Reworked extension inputs and outputs + +In previous versions of the frontend system you would define extension inputs and outputs as an "data map" where each named property corresponded to a piece of data that could be accessed via the same name in the extension factory. In order to better support extension overrides and blueprints, as well as reduce the risk of confusion, these data maps have been replaced by an array of data references. + +For example, an extension previously declared like this: + +```tsx +createExtension({ + name: 'example', + attachTo: { id: 'some-extension', input: 'content' }, + inputs: { + header: createExtensionInput( + { element: coreExtensionData.reactElement }, + { + optional: true, + singleton: true, + }, + ), + }, + output: { element: coreExtensionData.reactElement }, + factory({ inputs }) { + return { + element: , + }; + }, +}); +``` + +Would now look like this: + +```tsx +createExtension({ + name: 'example', + attachTo: { id: 'some-extension', input: 'content' }, + inputs: { + header: createExtensionInput([coreExtensionData.reactElement], { + optional: true, + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( + , + ), + ]; + }, +}); +``` + +Note the changes to the `inputs` and `output` declarations, as well as how these are used in the factory. Both the `inputs` and `output` now declare their expected data using an array, without names tied to each piece of data. The `get` method on the input object is used to access the input data, and accept a data reference matching the data that was declared for that input. + +The biggest change in practice is how the extension factories output their data. Instead of returning an object with the data values, you instead use each extension data reference to encapsulate a value and return a collection of these encapsulated values from the factory. + +### Blueprints instead of extension creators + +The "extension creator" pattern where `createExtension` was wrapped up in a function for creating specific kind of extensions has been replaced by [extension blueprints](./23-extension-blueprints.md). Extension creators were hard to implement and to maintain, with blueprints providing a much more consistent and powerful API surface for both plugin builders and integrators. For example, `createPageExtension` was the extension creator equivalent of `PageBlueprint`. + +Replace all existing usages of extension creators in your plugin or app with the corresponding blueprint. For a given extension creator `createExtension`, the blueprint equivalent is `Blueprint`. There might be slight differences in the API between the two, but most often you just need to move the kind-specific options of the extension creator to be passed as parameters to the blueprint. Note that if your extension declared any additional inputs or config you'll need to use the [`.makeWithOverrides`](./23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides) method of the blueprint. + +If your plugin exports and extension creators, these should be migrated to blueprints instead. + +### Extension tester rework + +The `createExtensionTester` from the `@backstage/frontend-test-utils` package has been reworked to better support testing of extensions. The new API allows access to extension output directly using the new `.get(ref)` method, and also provides access to all tested extensions through the new `.query(id/extension)` method. + +The `.render()` method that used to render the test subject in a test app has been deprecated. The extension tester no longer constructs a full app tree, but instead only instantiates the tree for the extensions under test. If you want to test the rendering of an extension that outputs in an app, you can instead use the `renderInTestApp` utility in combination with the new `.reactElement()` method of the extension tester: `renderInTestApp(tester.reactElement())`. + +### Extension data references update + +The way that extension data references are declared has been changed to allow for type inference of the ID. This requires the declaration to be split into two separate function calls, one to supply the type and the other to infer any options. For example, a reference that was previously declared like this: + +```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', +}); +``` diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index ea7fe20948..5b5298a2c5 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -30,13 +30,13 @@ The created-app is currently templated for legacy frontend system applications, ## The app instance -The starting point of a frontend app is the `createApp` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-app-api`, which is where you will find most of the common APIs for building apps. +The starting point of a frontend app is the `createApp` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-defaults`, which is where you will find most of the common APIs for building apps. This is how to create a minimal app: ```tsx title="in src/index.ts" import ReactDOM from 'react-dom/client'; -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; import catalogPlugin from '@backstage/plugin-catalog/alpha'; // Create your app instance @@ -66,7 +66,7 @@ Linking routes from different plugins requires this configuration. You can do th ### Enable feature discovery -Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/02-app.md#feature-discovery) for more details. +Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/10-app.md#feature-discovery) for more details. :::warning Remember that package extensions that are not auto-discovered must be manually added to the application when creating an app. See [features](#install-features-manually) for more details. @@ -78,7 +78,7 @@ It is possible to enable, disable and configure extensions individually in the ` ### Customize or override built-in extensions -Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See [here](../building-plugins/03-extension-types.md) which types of extensions are available for you to customize your application. +Previously you would customize the application routes, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want to allow the same thing to be achieved while writing less code and instead installing more extensions to customize your Backstage instance. See the [extension blueprints](../building-plugins/03-common-extension-blueprints.md) section for a list of common extension kinds that are available for you to customize and extend your application. ## Use code to customize the app at a more granular level @@ -87,7 +87,7 @@ Previously you would customize the application route, components, apis, sidebar, A manual installation is required if your packages are not discovered automatically, either because you are not using `@backstage/cli` to build your application or because the features are defined in local modules in the app package. In order to manually install a feature, you must import it and pass it to the `createApp` function: ```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; // This plugin was create as a local module in the app import { somePlugin } from './plugins'; @@ -99,15 +99,15 @@ export default app.createRoot(); ``` :::info -You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/05-extension-overrides.md) documentation. +You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/25-extension-overrides.md) documentation. ::: ### Using an async features loader -In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-app-api.createappfeatureloader/) object and pass it to the `features` option: +In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-defaults.createappfeatureloader/) object and pass it to the `features` option: ```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; const app = createApp({ features: { @@ -129,7 +129,7 @@ export default app.createRoot(); In some cases we want to load our configuration from a backend server and to do so, you can pass an callback to the `configLoader` option when calling the `createApp` function, the callback should return a promise of an object with the config object: ```tsx title="packages/app/src/App.tsx" -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; import { getConfigFromServer } from './utils'; // Example lazy loading the app configuration diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index a5e6476eb0..5f74100df0 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -24,7 +24,7 @@ Be careful when disabling built-in extensions, as there may be other extensions ## Override built-in extensions -You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/05-extension-overrides.md) documentation on how to override application extensions. +You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/25-extension-overrides.md) documentation on how to override application extensions. :::warning Be aware there could be some implementation requirements to properly override an built-in extension, such as using same apis and do not remove inputs or configurations otherwise you can cause a side effect in other parts of the system that expects same minimal behavior. diff --git a/docs/frontend-system/building-apps/06-plugin-conversion.md b/docs/frontend-system/building-apps/06-plugin-conversion.md new file mode 100644 index 0000000000..5e7d431e73 --- /dev/null +++ b/docs/frontend-system/building-apps/06-plugin-conversion.md @@ -0,0 +1,87 @@ +--- +id: plugin-conversion +title: Converting 3rd-party plugins from the old system +sidebar_label: Converting 3rd-party Plugins +# prettier-ignore +description: Documentation for how to convert 3rd-party plugins to support the new frontend system. +--- + +If you are using or want to use a 3rd-party plugin that does not yet support the new frontend system in your app, you can often use conversion utilities from `@backstage/core-compat-api` in order wrap the plugin to make it possible to install in your app. + +> [!CAUTION] +> The purpose of these utilities is to wrap 3rd-party plugins. Do not use them for your own plugins where you can add support for the new frontend system directly. + +## Converting a legacy plugin + +The `@backstage/core-compat-api` package exports the `convertLegacyPlugin` function that can be used to convert a legacy plugin to the new frontend system. Simply pass in the old plugin instance and the new plugin will be returned. + +```ts +import { techdocsPlugin } from '@backstage/plugin-techdocs'; + +// TechDocs is used as an example here, it already supports +// the new frontend system so don't use this code directly. +const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { + extensions: [], +}); +``` + +Note that when using `convertLegacyPlugin` we also have to pass an array of extensions. This is because the `convertLegacyPlugin` function does not have access to or know about the different types of extensions in the old system. You instead need to manually convert each of the old extensions that you want to include in the plugin instance. + +## Converting legacy extensions + +As mentioned above, you need to manually convert each of the old extensions that you want to include in the plugin instance. The `@backstage/core-compat-api` package exports `convertLegacyPageExtension` for this purpose, which can be used to convert a top-level page extension to the new system. Simply pass in the old extension and the new extension will be returned. + +```ts +const convertedIndexPage = convertLegacyPageExtension(TechDocsIndexPage); +``` + +### Overriding inferred parameters + +The conversion functions such as `convertLegacyPageExtension` will attempt to infer parameters from the old extension, in particular it uses the name of the old extension to determine things like the name and route paths of the new extension. In some cases you may need to supply your own parameters to the conversion function if the inferred values are not correct. + +```ts +const convertedIndexPage = convertLegacyPageExtension(TechDocsIndexPage, { + name: 'index', + defaultPath: '/docs', +}); +``` + +### Other types of extensions + +The `@backstage/core-compat-api` only provides conversion functions for the built-in extension types, i.e. only page extensions. There are many plugins that define their own extension types, such as the entity content and card extensions for the catalog. For those cases it's up to the plugin libraries to provide their own conversion functions for these types of extensions. For example, `@backstage/plugin-catalog-react/alpha` provides both `convertLegacyEntityContentExtension` and `convertLegacyEntityCardExtension`. + +The following are the known conversion functions provided by various libraries: + +- `convertLegacyPageExtension` - `@backstage/core-compat-api` +- `convertLegacyEntityContentExtension` - `@backstage/plugin-catalog-react/alpha` +- `convertLegacyEntityCardExtension` - `@backstage/plugin-catalog-react/alpha` + +## Putting it all together + +Using the plugin converter along with extension converters from various libraries, we can not more fully convert our 3rd-party plugin to be able to install it in an app built with the new frontend system: + +```ts +import { + techdocsPlugin, + TechDocsIndexPage, + TechDocsReaderPage, + EntityTechdocsContent, +} from '@backstage/plugin-techdocs'; + +const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { + extensions: [ + convertLegacyPageExtension(TechDocsIndexPage, { + name: 'index', + defaultPath: '/docs', + }), + convertLegacyPageExtension(TechDocsReaderPage, { + defaultPath: '/docs/:namespace/:kind/:name/*', + }), + convertLegacyEntityContentExtension(EntityTechdocsContent), + ], +}); + +const app = createApp({ + features: [convertedTechdocsPlugin], +}); +``` diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 716e0da772..96e7dd2bd5 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -18,12 +18,12 @@ The first step in migrating an app is to switch out the `createApp` function for // highlight-remove-next-line import { createApp } from '@backstage/app-defaults'; // highlight-add-next-line -import { createApp } from '@backstage/frontend-app-api'; +import { createApp } from '@backstage/frontend-defaults'; ``` This immediate switch will lead to a lot of breakages that we need to fix. -Let's start by addressing the change to `app.createRoot(...)`, which no longer accepts any arguments. This represents a fundamental change that the new frontend system introduces. In the old system the app element tree that you passed to `app.createRoot(...)` was the primary way that you installed and configured plugins and features in your app. In the new system this is instead replaced by extensions that are wired together into an extension tree. Much more responsibility has now been shifted to plugins, for example you no longer have to manually provide the route path for each plugin page, but instead only configure it if you want to override the default. For more information on how the new system works, see the [architecture](../architecture/01-index.md) section. +Let's start by addressing the change to `app.createRoot(...)`, which no longer accepts any arguments. This represents a fundamental change that the new frontend system introduces. In the old system the app element tree that you passed to `app.createRoot(...)` was the primary way that you installed and configured plugins and features in your app. In the new system this is instead replaced by extensions that are wired together into an extension tree. Much more responsibility has now been shifted to plugins, for example you no longer have to manually provide the route path for each plugin page, but instead only configure it if you want to override the default. For more information on how the new system works, see the [architecture](../architecture/00-index.md) section. Given that the app element tree is most of what builds up the app, it's likely also going to be the majority of the migration effort. In order to make the migration as smooth as possible we have provided a helper that lets you convert an existing app element tree into plugins that you can install in a new app. This in turn allows for a gradual migration of individual plugins, rather than needing to migrate the entire app structure at once. @@ -95,7 +95,7 @@ At this point the contents of your app should be past the initial migration stag ## Migrating `createApp` Options -Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension creator](../architecture/03-extensions.md#extension-creators) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/05-extension-overrides.md#create-standalone-extensions) section for more information. +Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension blueprint](../architecture/23-extension-blueprints.md) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/25-extension-overrides.md#creating-a-standalone-extension-bundle) section for more information. For example, assuming you have a `lightTheme` extension that you want to add to your app, you can use the following: @@ -115,7 +115,7 @@ You can then also add any additional extensions that you may need to create as p ### `apis` -[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). +[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `ApiBlueprint` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). For example, the following `apis` configuration: @@ -134,12 +134,15 @@ const app = createApp({ Can be converted to the following extension: ```ts -const scmIntegrationsApi = createApiExtension({ - factory: createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), +const scmIntegrationsApi = ApiBlueprint.make({ + name: 'scm-integrations', + params: { + factory: createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + }, }); ``` @@ -176,7 +179,7 @@ createApp({ }); ``` -Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled. +Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/10-app.md#feature-discovery) is enabled. ```yaml title="in app-config.yaml" app: @@ -207,7 +210,7 @@ createApp({ Can be converted to the following plugin configuration: ```tsx -createPlugin({ +createFrontendPlugin({ id: 'tech-radar', // ... featureFlags: [{ name: 'tech-radar' }], @@ -219,9 +222,9 @@ createPlugin({ Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information. -The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`. +The `Router` component is now a built-in extension that you can [override](../architecture/25-extension-overrides.md) using `createRouterExtension`. -The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead. +The Sign-in page is now installed as an extension, created using the `SignInPageBlueprint` instead. For example, the following sign-in page configuration: @@ -246,25 +249,27 @@ const app = createApp({ Can be converted to the following extension: ```tsx -const signInPage = createSignInPageExtension({ - loader: async () => props => - ( - - ), +const signInPage = SignInPageBlueprint.make({ + params: { + loader: async () => props => + ( + + ), + }, }); ``` ### `themes` -Themes are now installed as extensions, using `createThemeExtension`. +Themes are now installed as extensions, created using `ThemeBlueprint`. For example, the following theme configuration: @@ -287,14 +292,19 @@ const app = createApp({ Can be converted to the following extension: ```tsx -const lightTheme = createThemeExtension({ - id: 'light', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), +const lightTheme = ThemeBlueprint.make({ + name: 'light', + params: { + theme: { + id: 'light', + title: 'Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, }); ``` @@ -341,7 +351,7 @@ const app = createApp({ ### `bindRoutes` -Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information. +Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/36-routes.md#binding-external-route-references) for more information. Note that if you are binding routes from a legacy plugin that was converted using `convertLegacyApp`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. @@ -360,7 +370,7 @@ const app = createApp({ ### `__experimentalTranslations` -Translations are now installed as extensions, using `createTranslationExtension`. +Translations are now installed as extensions, created using `TranslationBlueprint`. For example, the following translations configuration: @@ -383,11 +393,14 @@ createApp({ Can be converted to the following extension: ```tsx -createTranslationExtension({ - resource: createTranslationMessages({ - ref: catalogTranslationRef, - catalog_page_create_button_title: 'Create Software', - }), +TranslationBlueprint.make({ + name: 'catalog-overrides', + params: { + resource: createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), + }, }); ``` @@ -470,7 +483,7 @@ const routes = ( ); ``` -If you are using [app feature discovery](../architecture/02-app.md#feature-discovery) the installation step is simple, it's already done! The new version of the scaffolder plugin was already discovered and present in the app, it was simply disabled because the plugin created from the legacy route had higher priority. If you do not use feature discovery, you will instead need to manually install the new scaffolder plugin in your app through the `features` option of `createApp`. +If you are using [app feature discovery](../architecture/10-app.md#feature-discovery) the installation step is simple, it's already done! The new version of the scaffolder plugin was already discovered and present in the app, it was simply disabled because the plugin created from the legacy route had higher priority. If you do not use feature discovery, you will instead need to manually install the new scaffolder plugin in your app through the `features` option of `createApp`. Continue this process for each of your legacy routes until you have migrated all of them. For any plugin with additional extensions installed as children of the `Route`, refer to the plugin READMEs for more detailed instructions. For the entity pages, refer to the [separate section](#entity-pages). @@ -482,24 +495,22 @@ The entity pages are typically defined in `packages/app/src/components/catalog` New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box. -Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md): +Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/plugins/app/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/25-extension-overrides.md): ```tsx const nav = createExtension({ namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: ( + return [ + coreExtensionData.reactElement( {/* Sidebar contents from packages/app/src/components/Root.tsx go here */} - + , ), - }; + ]; }, }); ``` @@ -548,7 +559,7 @@ export default app.createRoot( ); ``` -Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. +Any app root wrapper needs to be migrated to be an extension, created using `AppRootWrapperBlueprint`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. Here is an example converting the `CustomAppBarrier` into extension: @@ -558,11 +569,13 @@ createApp({ features: [ createExtensionOverrides({ extensions: [ - createAppRootWrapperExtension({ - name: 'CustomAppBarrier', - // Whenever your component uses legacy core packages, wrap it with "compatWrapper" - // e.g. props => compatWrapper() - Component: CustomAppBarrier, + AppRootWrapperBlueprint.make({ + name: 'custom-app-barrier', + params: { + // Whenever your component uses legacy core packages, wrap it with "compatWrapper" + // e.g. props => compatWrapper() + Component: CustomAppBarrier, + }, }), ], }), diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index f8d80f9382..633afd6ce8 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -8,9 +8,9 @@ description: Building frontend plugins using the new frontend system > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -This section covers how to build your own frontend [plugins](../architecture/04-plugins.md) and -[overrides](../architecture/05-extension-overrides.md). They are sometimes collectively referred to as -frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/02-app.md). +This section covers how to build your own frontend [plugins](../architecture/15-plugins.md) and +[overrides](../architecture/25-extension-overrides.md). They are sometimes collectively referred to as +frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/10-app.md). ## Creating a new plugin @@ -24,14 +24,14 @@ The created plugin will currently be templated for use in the legacy frontend sy ## The plugin instance -The starting point of a frontend plugin is the `createPlugin` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-plugin-api`, which is where you will find most of the common APIs for building plugins. +The starting point of a frontend plugin is the `createFrontendPlugin` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-plugin-api`, which is where you will find most of the common APIs for building plugins. This is how to create a minimal plugin: ```tsx title="in src/plugin.ts" -import { createPlugin } from '@backstage/frontend-plugin-api'; +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; -export const examplePlugin = createPlugin({ +export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [], }); @@ -43,13 +43,13 @@ export { examplePlugin as default } from './plugin'; Note that we export the plugin as the default export of our package from `src/index.ts`. This is important, as it is how users of our plugin are able to seamlessly install the plugin package in a Backstage app without having to reference the plugin instance through code. -The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. +The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/50-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. ## Adding extensions -The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. +The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/20-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. -To create a new extension you typically use pre-defined [extension creators](../architecture/03-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/07-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. +To create a new extension you typically use pre-defined [extension blueprints](../architecture/23-extension-blueprints.md), provided either by the framework itself or by other plugins. In this case we'll use `PageBlueprint` and `NavItemBlueprint`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/36-routes.md#creating-a-route-reference) to use as a reference for our page, allowing us to dynamically create URLs that link to our page. ```tsx title="in src/routes.ts" import { createRouteRef } from '@backstage/frontend-plugin-api'; @@ -63,36 +63,41 @@ export const rootRouteRef = createRouteRef(); ```tsx title="in src/plugin.ts" import { - createPlugin, - createPageExtension, - createNavItemExtension, + createFrontendPlugin, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from './routes'; // Note that these extensions aren't exported, only the plugin itself is. // You can export it locally for testing purposes, but don't export it from the plugin package. -const examplePage = createPageExtension({ - routeRef: rootRouteRef, +const examplePage = PageBlueprint.make({ + params: { + routeRef: rootRouteRef, - // This is the default path of this page, but integrators are free to override it - defaultPath: '/example', + // This is the default path of this page, but integrators are free to override it + defaultPath: '/example', - // Page extensions are always dynamically loaded using React.lazy(). - // All of the functionality of this page is implemented in the - // ExamplePage component, which is a regular React component. - // highlight-next-line - loader: () => import('./components/ExamplePage').then(m => ), + // Page extensions are always dynamically loaded using React.lazy(). + // All of the functionality of this page is implemented in the + // ExamplePage component, which is a regular React component. + // highlight-next-line + loader: () => + import('./components/ExamplePage').then(m => ), + }, }); // This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item -const exampleNavItem = createNavItemExtension({ - routeRef: rootRouteRef, - title: 'Example', - icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library +const exampleNavItem = NavItemBlueprint.make({ + params: { + routeRef: rootRouteRef, + title: 'Example', + icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library + }, }); // The same plugin as above, now with the extensions added -export const examplePlugin = createPlugin({ +export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [examplePage, exampleNavItem], // We can also make routes available to other plugins. @@ -106,7 +111,7 @@ export const examplePlugin = createPlugin({ What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. -We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-route-references) section. +We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/36-routes.md#external-route-references) section. ## Utility APIs @@ -150,25 +155,25 @@ export function ExamplePage() { ``` ```tsx title="in src/plugin.ts - Registering a factory for our API" -import { - createApiFactory, - createApiExtension, -} from '@backstage/frontend-plugin-api'; +import { createApiFactory, ApiBlueprint } from '@backstage/frontend-plugin-api'; import { exampleApiRef, DefaultExampleApi } from './api'; // highlight-add-start -const exampleApi = createApiExtension({ - factory: createApiFactory({ - api: exampleApiRef, - deps: {}, - factory: () => new DefaultExampleApi(), - }), +const exampleApi = ApiBlueprint.make({ + name: 'example', + params: { + factory: createApiFactory({ + api: exampleApiRef, + deps: {}, + factory: () => new DefaultExampleApi(), + }), + }, }); // highlight-add-end /* Omitted definitions for examplePage, exampleNavItem, and rootRouteRef. */ -export const examplePlugin = createPlugin({ +export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [ // highlight-add-next-line @@ -187,26 +192,28 @@ export const examplePlugin = createPlugin({ There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](../../features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. ```tsx title="in src/plugin.ts - An example entity content extension" -import { createEntityContentExtension } from '@backstage/plugin-catalog-react'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; // Entity content extensions are similar to page extensions in that they are rendered at a route, // although they also have a title to support in-line navigation between the different content. // Just like a page extension the content is lazy loaded, and you can also provide a // route reference if you want to be able to generate a URL that links to the content. -const exampleEntityContent = createEntityContentExtension({ - defaultPath: 'example', - defaultTitle: 'Example', - loader: () => - import('./components/ExampleEntityContent').then(m => ( - - )), +const exampleEntityContent = EntityContentBlueprint.make({ + params: { + defaultPath: 'example', + defaultTitle: 'Example', + loader: () => + import('./components/ExampleEntityContent').then(m => ( + + )), + }, }); -export const examplePlugin = createPlugin({ +export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [ // highlight-add-next-line - exampleEntityContent + exampleEntityContent, exampleApi, examplePage, exampleNavItem, @@ -219,4 +226,4 @@ export const examplePlugin = createPlugin({ The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). -For a more complete list of the different types of extensions that you can create for your plugin, see the [extension types](./03-extension-types.md) section. +For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-common-extension-blueprints.md) section. diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index 26c9503c6d..225c570fbd 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -41,7 +41,7 @@ describe('Entity details component', () => { }); ``` -To mock [Utility APIs](../architecture/06-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API: +To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API: ```tsx import React from 'react'; @@ -85,6 +85,8 @@ describe('Entity details component', () => { }); ``` +This pattern also works for many other context providers. An important example is the `EntityProvider` from the `@backstage/plugin-catalog-react` package, which you can use to provide a mocked entity context to the component. + ## Testing extensions To facilitate testing of frontend extensions, the `@backstage/frontend-test-utils` package provides a tester class which starts up an entire frontend harness, complete with a number of default features. You can then provide overrides for extensions whose behavior you need to adjust for the test run. @@ -93,7 +95,7 @@ A number of features (frontend extensions and overrides) are also accepted by th ### Single extension -In order to test an extension in isolation, you simply need to pass it into the tester factory, then call the render method on the returned instance: +In order to test an extension in isolation, you can use `createExtensionTester` to create a tester instance and access the element that the extension outputs. This element can then be rendered as usual with `renderInTestApp`: ```tsx import { screen } from '@testing-library/react'; @@ -101,90 +103,49 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { indexPageExtension } from './plugin'; describe('Index page', () => { - it('should render a the index page', () => { - createExtensionTester(indexPageExtension).render(); + it('should render a the index page', async () => { + await renderInTestApp( + createExtensionTester(indexPageExtension).reactElement(), + ); expect(screen.getByText('Index Page')).toBeInTheDocument(); }); }); ``` -### Extension preset +This pattern also allows you to wrap the extension with context providers, such as the `TestApiProvider` that was introduced [above](#testing-react-components). -There are some extensions that rely on other extensions existence, such as a page that links to another page. In that case, you can add more than one extension to the preset of features you want to render in the test, as shown below: +Note that the `.reactElement()` method will look for the `coreExtensionData.reactElement` data in the extension outputs. If that doesn't exist and the extension outputs something else that you want to test, you can access the output data using the `.get(dataRef)` method instead. + +### Multiple extensions + +In some cases you might need to test multiple extensions together, in particular when testing inputs. In this case, you can add more extensions to the tester instance using the `.add(...)` method. It also accepts an optional options object as the second argument, which you can use to provide configuration for the extension instance. ```tsx import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { indexPageExtension, detailsPageExtension } from './plugin'; +import { indexPageExtension, indexPageHeader } from './plugin'; describe('Index page', async () => { - it('should link to the details page', () => { - createExtensionTester(indexPageExtension) - // Adding more extensions to the preset being tested - .add(detailsPageExtension) - .render(); + it('should link to the index page with header', async () => { + const tester = createExtensionTester(indexPageExtension) + // Adding the header to be rendered on the index page + .add(indexPageHeader); - await expect(screen.findByText('Index Page')).toBeInTheDocument(); + await renderInTestApp(tester.reactElement()); - await userEvent.click(screen.getByRole('link', { name: 'See details' })); + await expect(screen.findByText('Index page')).toBeInTheDocument(); + await expect(screen.findByText('Index page header')).toBeInTheDocument(); - await expect( - screen.findByText('Details Page'), - ).resolves.toBeInTheDocument(); + expect( + tester.query(indexPageHeader).get(headerDataRef), + ).toMatchObject(/* ... */); }); }); ``` -### Mocking apis - -If your extensions requires implementation of APIs that aren't wired up by default, you'll have to add overrides to the preset of features being tested: - -```tsx -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { createApiFactory } from '@backestage/core-plugin-api'; -import { - createExtensionOverrides, - configApiRef, - analyticsApiRef, -} from '@backstage/frontend-plugin-api'; -import { - createExtensionTester, - MockConfigApi, - MockAnalyticsApi, -} from '@backstage/frontend-test-utils'; -import { indexPageExtension } from './plugin'; - -describe('Index page', () => { - it('should capture click events in analytics', async () => { - // Mocking the analytics api implementation - const analyticsApiMock = new MockAnalyticsApi(); - - const analyticsApiOverride = createApiExtension({ - factory: createApiFactory({ - api: analyticsApiRef, - factory: () => analyticsApiMock, - }), - }); - - createExtensionTester(indexPageExtension) - // Overriding the analytics api extension - .add(analyticsApiOverride) - .render(); - - await userEvent.click( - await screen.findByRole('link', { name: 'See details' }), - ); - - expect(analyticsApiMock.getEvents()[0]).toMatchObject({ - action: 'click', - subject: 'See details', - }); - }); -}); -``` +When testing multiple extensions you may sometimes want to access the output of other extensions than the main test subject. You can use the `.query(ext)` method to query a different extension that has been added to the tester, by passing the extension used with the `createExtensionTester(...).add(ext)` ### Setting configuration @@ -198,36 +159,26 @@ import { indexPageExtension, detailsPageExtension } from './plugin'; describe('Index page', () => { it('should accepts a custom title via config', async () => { - createExtensionTester(indexPageExtension, { - // Configuration specific of index page - config: { title: 'Custom index' }, - }) - .add(detailsExtensionPage, { - // Configuration specific of details page - config: { title: 'Custom details' }, - }) - .render({ - // Configuration specific of the instance - config: { - app: { - title: 'Custom app', - }, + const tester = createExtensionTester(indexPageExtension, { + // Extension configuration for the index page + config: { title: 'Custom page' }, + }).add(indexPageHeader, { + // Extension configuration for the index page header + config: { title: 'Custom page header' }, + }); + + await renderInTestApp(tester.reactElement(), { + // Global configuration for the app + config: { + app: { + title: 'Custom app', }, - }); + }, + }); - await expect( - screen.findByRole('heading', { name: 'Custom app' }), - ).resolves.toBeInTheDocument(); - - await expect( - screen.findByRole('heading', { name: 'Custom index' }), - ).resolves.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('link', { name: 'See details' })); - - await expect( - screen.findByText('Custom details'), - ).resolves.toBeInTheDocument(); + await expect(screen.findByText('Custom app')).toBeInTheDocument(); + await expect(screen.findByText('Custom page')).toBeInTheDocument(); + await expect(screen.findByText('Custom page header')).toBeInTheDocument(); }); }); ``` diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md similarity index 64% rename from docs/frontend-system/building-plugins/03-extension-types.md rename to docs/frontend-system/building-plugins/03-common-extension-blueprints.md index 1f3baaa710..5193fd85b1 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -1,40 +1,40 @@ --- -id: extension-types -title: Frontend System Extension Types -sidebar_label: Extension Types +id: common-extension-blueprints +title: Common Extension Blueprints +sidebar_label: Common Extension Blueprints # prettier-ignore -description: Extension types provided by the frontend system and core features +description: Extension blueprints provided by the frontend system and core features --- > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -This section covers many of the [extension types](../architecture/03-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. +This section covers many of the [extension blueprints](../architecture/23-extension-blueprints.md) available at your disposal when building Backstage frontend plugins. -## Built-in extension types +## Built-in extension blueprints -These are the extension types provided by the Backstage frontend framework itself. +These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage frontend framework itself. -### Api - [Reference](../../reference/frontend-plugin-api.createapiextension.md) +### Api - [Reference](../../reference/frontend-plugin-api.apiblueprint.md) An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. ### Component - [Reference](../../reference/frontend-plugin-api.createcomponentextension.md) -Components extensions are used to override the component associated with a component reference throughout the app. +Components extensions are used to override the component associated with a component reference throughout the app. This uses an extension creator function rather than a blueprint, but will likely be migrated to a blueprint in the future. -### NavItem - [Reference](../../reference/frontend-plugin-api.createnavitemextension.md) +### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint.md) Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. -### Page - [Reference](../../reference/frontend-plugin-api.createpageextension.md) +### Page - [Reference](../../reference/frontend-plugin-api.pageblueprint.md) Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. -### SignInPage - [Reference](../../reference/frontend-plugin-api.createsigninpageextension.md) +### SignInPage - [Reference](../../reference/frontend-plugin-api.signinpageblueprint.md) Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. -### Theme - [Reference](../../reference/frontend-plugin-api.createthemeextension.md) +### Theme - [Reference](../../reference/frontend-plugin-api.themeblueprint.md) Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. @@ -42,22 +42,22 @@ Theme extensions provide custom themes for the app. They are always attached to Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app. -### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) +### Translation - [Reference](../../reference/frontend-plugin-api.translationblueprint.md) Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. -## Core feature extension types +## Core feature extension blueprints -These are the extension types provided by the Backstage core feature plugins. +These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage core feature plugins. ### EntityCard - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) -Creates entity cards to be displayed on the entity pages of the catalog plugin. +Creates entity cards to be displayed on the entity pages of the catalog plugin. Exported as `EntityCardBlueprint`. ### EntityContent - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) -Creates entity content to be displayed on the entity pages of the catalog plugin. +Creates entity content to be displayed on the entity pages of the catalog plugin. Exported as `EntityContentBlueprint`. ### SearchResultListItem - [Reference](https://github.com/backstage/backstage/blob/master/plugins/search-react/api-report-alpha.md) -Creates search result list items for different types of search results, to be displayed in search result lists. +Creates search result list items for different types of search results, to be displayed in search result lists. Exported as `SearchResultListItemBlueprint`. diff --git a/docs/frontend-system/building-plugins/04-built-in-data-refs.md b/docs/frontend-system/building-plugins/04-built-in-data-refs.md index dacbbf30f1..a26f6549b7 100644 --- a/docs/frontend-system/building-plugins/04-built-in-data-refs.md +++ b/docs/frontend-system/building-plugins/04-built-in-data-refs.md @@ -8,7 +8,7 @@ description: Configuring or overriding built-in extension data references > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -To have a better understanding of extension data references please read [the corresponding architecture section](../architecture/03-extensions.md#extension-data) first. +To have a better understanding of extension data references please read [the corresponding architecture section](../architecture/20-extensions.md#extension-data) first. ## Built-in extension data references @@ -24,18 +24,15 @@ The `reactElement` data reference can be used for defining the extension input/o ```tsx import { + createExtension, coreExtensionData, - createExtensionInput, - createPageExtension, } from '@backstage/frontend-plugin-api'; -const homePage = createPageExtension({ - defaultPath: '/home', - routeRef: rootRouteRef, - inputs: { - props: createExtensionInput({ - children: coreExtensionData.reactElement.optional(), - }), +const examplePage = createExtension({ + name: 'example', + output: [coreExtensionData.reactElement], + factor() { + return [coreExtensionData.reactElement(

Example

)]; }, }); ``` diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 16f465ece7..cf4d82002a 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -8,7 +8,7 @@ description: How to migrate an existing frontend plugin to the new frontend syst This guide allows you to migrate a frontend plugin and its own components, routes, apis to the new frontend system. -The main concept is that routes, components, apis are now extensions. You can use the appropriate extension creators to migrate all of them to extensions. +The main concept is that routes, components, apis are now extensions. You can use the appropriate [extension blueprints](../architecture/23-extension-blueprints.md) to migrate all of them to extensions. ## Migrating the plugin @@ -29,13 +29,13 @@ In the legacy frontend system a plugin was defined in its own `plugin.ts` file a }); ``` -In order to migrate the actual definition of the plugin you need to recreate the plugin using the new `createPlugin` utility exported by `@backstage/frontend-plugin-api`. -The new `createPlugin` function doesn't accept apis anymore as apis are now extensions. +In order to migrate the actual definition of the plugin you need to recreate the plugin using the new `createFrontendPlugin` utility exported by `@backstage/frontend-plugin-api`. +The new `createFrontendPlugin` function doesn't accept apis anymore as apis are now extensions. ```ts title="my-plugin/src/alpha.ts" - import { createPlugin } from '@backstage/frontend-plugin-api'; + import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; - export default createPlugin({ + export default createFrontendPlugin({ id: 'my-plugin', // bind all the extensions to the plugin /* highlight-next-line */ @@ -77,7 +77,7 @@ The code above binds all the extensions to the plugin. _Important_: Make sure to ## Migrating Pages -Pages that were previously created using the `createRoutableExtension` extension function can be migrated to the new Frontend System using the `createPageExtension` extension creator, exported by `@backstage/frontend-plugin-api`. +Pages that were previously created using the `createRoutableExtension` extension function can be migrated to the new Frontend System using the `PageBlueprint` [extension blueprint](../architecture/23-extension-blueprints.md), exported by `@backstage/frontend-plugin-api`. For example, given the following page: @@ -94,33 +94,35 @@ export const FooPage = fooPlugin.provide( it can be migrated as the following: ```tsx -import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -const fooPage = createPageExtension({ - defaultPath: '/foo', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootRouteRef), - // these inputs usually match the props required by the component. - loader: ({ inputs }) => - import('./components/').then(m => - // The compatWrapper utility allows you to use the existing - // legacy frontend utilities used internally by the components. - compatWrapper(), - ), +const fooPage = PageBlueprint.make({ + params: { + defaultPath: '/foo', + // you can reuse the existing routeRef + // by wrapping into the convertLegacyRouteRef. + routeRef: convertLegacyRouteRef(rootRouteRef), + // these inputs usually match the props required by the component. + loader: ({ inputs }) => + import('./components/').then(m => + // The compatWrapper utility allows you to use the existing + // legacy frontend utilities used internally by the components. + compatWrapper(), + ), + }, }); ``` -then add the `fooPage` extension to the plugin: +Then add the `fooPage` extension to the plugin: ```ts title="my-plugin/src/alpha.ts" - import { createPlugin } from '@backstage/frontend-plugin-api'; + import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; - export default createPlugin({ + export default createFrontendPlugin({ id: 'my-plugin', // bind all the extensions to the plugin /* highlight-remove-next-line */ @@ -133,7 +135,7 @@ then add the `fooPage` extension to the plugin: ## Migrating Components -The equivalent utility to replace components created with `createComponentExtension` is `createExtension` from `@backstage/frontend-plugin-api`. However, we recommend searching for a more appropriate extension creator first. +The equivalent utility to replace components created with `createComponentExtension` depends on the context within which the component is used, typically indicated by the naming pattern of the export. Many of these can be migrated to one of the [existing blueprints](03-common-extension-blueprints.md), but in rare cases it may be necessary to use [`createExtension`](../architecture/20-extensions.md#creating-an-extension) directly. ## Migrating APIs @@ -182,7 +184,7 @@ const exampleWorkApi = createApiFactory({ The major changes we'll make are - Change the old `@backstage/core-plugin-api` imports to the new `@backstage/frontend-plugin-api` package as per the top section of this guide -- Wrap the existing API factory in a `createApiExtension` +- Wrap the existing API factory in a `ApiBlueprint` The end result, after simplifying imports and cleaning up a bit, might look like this: @@ -190,26 +192,28 @@ The end result, after simplifying imports and cleaning up a bit, might look like import { storageApiRef, createApiFactory, - createApiExtension, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { workApiRef } from '@internal/plugin-example-react'; import { WorkImpl } from './WorkImpl'; -const exampleWorkApi = createApiExtension({ - factory: createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new WorkImpl({ storageApi }), - }), +const exampleWorkApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), + }), + }, }); ``` Finally, let's add the `exampleWorkApi` extension to the plugin: ```ts title="my-plugin/src/alpha.ts" - import { createPlugin } from '@backstage/frontend-plugin-api'; + import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; - export default createPlugin({ + export default createFrontendPlugin({ id: 'my-plugin', // bind all the extensions to the plugin /* highlight-remove-next-line */ diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md index 37120a5152..07f7dcc2a8 100644 --- a/docs/frontend-system/utility-apis/01-index.md +++ b/docs/frontend-system/utility-apis/01-index.md @@ -8,7 +8,7 @@ description: Working with Utility APIs in the New Frontend System > **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. +As described [in the architecture section](../architecture/33-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/20-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. ## Creating utility APIs diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index fcb7ff1a65..01c21fe656 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -45,9 +45,9 @@ The plugin itself now wants to provide this API and its default implementation, ```tsx title="in @internal/plugin-example" import { - createApiExtension, + ApiBlueprint, createApiFactory, - createPlugin, + createFrontendPlugin, storageApiRef, StorageApi, } from '@backstage/frontend-plugin-api'; @@ -62,21 +62,24 @@ class WorkImpl implements WorkApi { } } -const exampleWorkApi = createApiExtension({ - factory: createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => { - return new WorkImpl({ storageApi }); - }, - }), +const workApi = ApiBlueprint.make({ + name: 'work', + params: { + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + return new WorkImpl({ storageApi }); + }, + }), + }, }); /** * The Example plugin. * @public */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'example', extensions: [exampleWorkApi], }); @@ -86,43 +89,37 @@ For illustration we make a skeleton implementation class and the API extension a The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. -The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/08-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. +The extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, a `/` separator, and lastly the name we used of the extension. In this case we end up with `api:example/work`. Check out [the naming patterns doc](../architecture/50-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. In case there is a single API that is a central to the functionality of the plugin, most typically an API client, you can choose to omit the name of the extension so that you end up with just `api:`. ## Adding configurability -Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's make the required additions to our original work example API. +Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's refactory the example above to also accept configuration, which will require us to use the [override method of the blueprint](../architecture/23-extension-blueprints.md#creating-an-extension-from-a-blueprint-with-overrides). ```tsx title="in @internal/plugin-example" -/* highlight-add-next-line */ -import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; - -const exampleWorkApi = createApiExtension({ - /* highlight-add-start */ - api: workApiRef, - configSchema: createSchemaFromZod(z => - z.object({ - goSlow: z.boolean().default(false), - }), - ), - /* highlight-add-end */ - /* highlight-remove-next-line */ - factory: createApiFactory({ - /* highlight-add-next-line */ - factory: ({ config }) => createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => { - /* highlight-add-start */ - if (config.goSlow) { - /* ... */ - } - /* highlight-add-end */ +const exampleWorkApi = ApiBlueprint.makeWithOverrides({ + config: { + schema: { + goSlow: z => z.boolean().default(false), }, - }), + }, + factory(originalFactory, { config }) { + return originalFactory({ + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + return new WorkImpl({ + storageApi, + goSlow: config.goSlow, + }); + }, + }), + }); + }, }); ``` -We wanted users to be able to set a `goSlow` extension config parameter for our API instances. So we passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual extension config values will then be passed in a type safe manner in to the `factory` which is now a callback, wherein we can do what we wish with them. When changing to the callback form, we also had to add a top level `api: workApiRef` under `createApiExtension`. +We wanted users to be able to set a `goSlow` extension config parameter for our API instances, which we declared in our new configuration schema. The actual extension config values will then be passed in a type safe manner in to the blueprint `factory`, wherein we can use them to create our API factory and pass as our blueprint parameters. Note that the expression "extension config" as used here, is _not_ the same thing as the `configApi` which gives you access to the full app-config. The extension config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the Configuring section](./04-configuring.md). @@ -130,11 +127,11 @@ Note also that the extension config schema contained a default value fo the `goS ## Adding inputs -Inputs are added to Utility APIs in the same way as other extension types: +Inputs are added to Utility APIs in the same way as other extension blueprints: -- Declaring a set of `inputs` on your extension -- If needed, create custom extension data types to be used in those inputs -- If needed, export an extension creator function for creating that particular attachment type +- Use `.makeWithOverrides` and declare a set of `inputs` for your extension. +- If needed, create custom extension data types to be used in those inputs. +- If needed, create and export an [extension blueprint](../architecture/23-extension-blueprints.md#creating-an-extension-blueprint) for creating that particular attachment type. This is a power use case and not very commonly used. diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index 5c53c8617e..bdf3b8dcdb 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -46,23 +46,25 @@ Your utility APIs can depend on other utility APIs in their factories. You do th ```tsx import { configApiRef, - createApiExtension, + ApiBlueprint, createApiFactory, discoveryApiRef, } from '@backstage/frontend-plugin-api'; import { MyApiImpl } from './MyApiImpl'; -const myApi = createApiExtension({ - factory: createApiFactory({ - api: myApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - }, - factory: ({ configApi, discoveryApi }) => { - return new MyApiImpl({ configApi, discoveryApi }); - }, - }), +const myApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: myApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ configApi, discoveryApi }) => { + return new MyApiImpl({ configApi, discoveryApi }); + }, + }), + }, }); ``` diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index 7acf23decf..e403104506 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -12,7 +12,7 @@ Utility APIs are extensions and can therefore optionally be amended with configu ## Configuring -To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/08-naming-patterns.md) for details. +To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/50-naming-patterns.md) for details. Our example work API from [the creating section](./02-creating.md) would have the ID `api:plugin.example.work`. You configure it and all other extensions under the `app.extensions` section of your app-config. @@ -37,7 +37,7 @@ Well written input-enabled extension often have extension creator functions that ## Replacing a Utility API implementation -Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/05-extension-overrides.md). +Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/25-extension-overrides.md). ```tsx title="in your app" /* highlight-add-start */ @@ -49,13 +49,13 @@ class CustomWorkImpl implements WorkApi { const myOverrides = createExtensionOverrides({ extensions: [ - createApiExtension({ - api: workApiRef, - factory: () => - createApiFactory({ + ApiBlueprint.make({ + params: { + factory: createApiFactory({ api: workApiRef, factory: () => new CustomWorkImpl(), }), + }, }), ], }); diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index 88e4a02ef2..19062904e2 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -76,3 +76,15 @@ While package duplication might be acceptable in many cases, you might want to deduplicate packages for the purpose of optimizing bundle size and installation speed. We recommend using deduplication utilities such as `yarn dedupe` to trim down the number of duplicate packages. + +## Proxy + +The Backstage CLI uses [global-agent](https://www.npmjs.com/package/global-agent) to configure HTTP/HTTPS proxy settings using environment variables. This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. + +### Example Configuration + +```bash +export GLOBAL_AGENT_HTTP_PROXY=http://proxy.company.com:8080 +export GLOBAL_AGENT_HTTPS_PROXY=https://secure-proxy.company.com:8080 +export GLOBAL_AGENT_NO_PROXY=localhost,internal.company.com +``` diff --git a/docs/integrations/aws-s3/discovery--old.md b/docs/integrations/aws-s3/discovery--old.md index 09b6eb6cc0..3a9d30a370 100644 --- a/docs/integrations/aws-s3/discovery--old.md +++ b/docs/integrations/aws-s3/discovery--old.md @@ -36,7 +36,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -56,7 +56,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index db9119680e..4cca448cf2 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -36,7 +36,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -56,7 +56,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md index 56a2fe559f..27532fe6ee 100644 --- a/docs/integrations/azure/discovery--old.md +++ b/docs/integrations/azure/discovery--old.md @@ -45,7 +45,7 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index c8a8a3af7c..357df7d553 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -45,7 +45,7 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 04dc111d30..6ba9c0aa1f 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -146,7 +146,7 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/bitbucketServer/discovery--old.md b/docs/integrations/bitbucketServer/discovery--old.md new file mode 100644 index 0000000000..d8ef11533b --- /dev/null +++ b/docs/integrations/bitbucketServer/discovery--old.md @@ -0,0 +1,123 @@ +--- +id: discovery +title: Bitbucket Server Discovery +sidebar_label: Discovery +# prettier-ignore +description: Automatically discovering catalog entities from repositories in Bitbucket Server +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +The Bitbucket Server integration has a special entity provider for discovering +catalog files located in Bitbucket Server. +The provider will search your Bitbucket Server account and register catalog files matching the configured path +as Location entity and via following processing steps add all contained catalog entities. +This can be useful as an alternative to static locations or manually adding things to the catalog. + +## Installation + +You will have to add the entity provider in the catalog initialization code of your +backend. The provider is not installed by default, therefore you have to add a +dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend +package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server +``` + +And then add the entity provider to your catalog builder: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { BitbucketServerEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-server'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-start */ + builder.addEntityProvider( + BitbucketServerEntityProvider.fromConfig(env.config, { + logger: env.logger, + scheduler: env.scheduler, + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +## Configuration + +To use the entity provider, you'll need a [Bitbucket Server integration set up](locations.md). + +Additionally, you need to configure your entity provider instance(s): + +```yaml title="app-config.yaml" +catalog: + providers: + bitbucketServer: + yourProviderId: # identifies your ingested dataset + host: 'bitbucket.mycompany.com' + catalogPath: /catalog-info.yaml # default value + filters: # optional + projectKey: '^apis-.*$' # optional; RegExp + repoSlug: '^service-.*$' # optional; RegExp + skipArchivedRepos: true # optional; boolean + schedule: # same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +- **`host`**: + The host of the Bitbucket Server instance, **note**: the host needs to registered as an integration as well, see [location](locations.md). +- **`catalogPath`** _(optional)_: + Default: `/catalog-info.yaml`. + Path where to look for `catalog-info.yaml` files. + When started with `/`, it is an absolute path from the repo root. +- **`filters`** _(optional)_: + - **`projectKey`** _(optional)_: + Regular expression used to filter results based on the project key. + - **`repoSlug`** _(optional)_: + Regular expression used to filter results based on the repo slug. + - **`skipArchivedRepos`** _(optional)_: + Boolean flag to filter out archived repositories. +- **`schedule`**: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. + +## Custom location processing + +The Bitbucket Server Entity Provider will by default emit a location for each +matching repository. However, it is possible to override this functionality and take full control of how each +matching repository is processed. + +`BitbucketServerEntityProvider.fromConfig` takes an optional parameter +`options.parser` where you can set your own parser to be used for each matched +repository. + +```typescript +const provider = BitbucketServerEntityProvider.fromConfig(env.config, { + logger: env.logger, + schedule: env.scheduler, + parser: async function* customLocationParser(options: { + location: LocationSpec; + client: BitbucketServerClient; + }) { + // Custom logic for interpreting the matching repository + // See defaultBitbucketServerLocationParser for an example + }, +}); +``` diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 99e04affa8..3bc37e4cb5 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -6,6 +6,10 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in Bitbucket Server --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + The Bitbucket Server integration has a special entity provider for discovering catalog files located in Bitbucket Server. The provider will search your Bitbucket Server account and register catalog files matching the configured path @@ -16,34 +20,21 @@ This can be useful as an alternative to static locations or manually adding thin You will have to add the entity provider in the catalog initialization code of your backend. The provider is not installed by default, therefore you have to add a -dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend -package. +dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend package. ```bash title="From your Backstage root directory" yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server ``` -And then add the entity provider to your catalog builder: +And update your backend by adding the following line: -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { BitbucketServerEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-server'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add( + import('@backstage/plugin-catalog-backend-module-bitbucket-server/alpha'), +); +/* highlight-add-end */ ``` ## Configuration @@ -63,7 +54,7 @@ catalog: projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp skipArchivedRepos: true # optional; boolean - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -92,27 +83,3 @@ catalog: The amount of time that should pass before the first invocation happens. - **`scope`** _(optional)_: `'global'` or `'local'`. Sets the scope of concurrency control. - -## Custom location processing - -The Bitbucket Server Entity Provider will by default emit a location for each -matching repository. However, it is possible to override this functionality and take full control of how each -matching repository is processed. - -`BitbucketServerEntityProvider.fromConfig` takes an optional parameter -`options.parser` where you can set your own parser to be used for each matched -repository. - -```typescript -const provider = BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - schedule: env.scheduler, - parser: async function* customLocationParser(options: { - location: LocationSpec; - client: BitbucketServerClient; - }) { - // Custom logic for interpreting the matching repository - // See defaultBitbucketServerLocationParser for an example - }, -}); -``` diff --git a/docs/integrations/github/discovery--old.md b/docs/integrations/github/discovery--old.md index 752aa51bde..901462b1cb 100644 --- a/docs/integrations/github/discovery--old.md +++ b/docs/integrations/github/discovery--old.md @@ -133,7 +133,7 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -249,7 +249,7 @@ schedule: timeout: { minutes: 3 } ``` -More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. +More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication which carries a much higher rate limit at GitHub. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 43e63a618c..b2e998813d 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -83,7 +83,7 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex - schedule: # same options as in TaskScheduleDefinition + schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -203,7 +203,7 @@ schedule: timeout: { minutes: 3 } ``` -More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. +More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication which carries a much higher rate limit at GitHub. diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 18865cd630..61e42567a9 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -94,7 +94,7 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru - `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts. - `githubUrl`: The target that this provider should consume - `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). -- `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/) +- `schedule`: The refresh schedule to use, matches the structure of [`SchedulerServiceTaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinitionconfig/) ### Events Support diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 70f12f181e..79bd37061a 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -154,7 +154,7 @@ catalog: entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything excludeRepos: [] # Optional. A list of project paths that should be excluded from discovery, e.g. group/subgroup/repo. Should not start or end with a slash. - schedule: # Same options as in TaskScheduleDefinition. Optional for the Legacy Backend System + schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 303a4db752..aa1b49b41c 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -176,7 +176,7 @@ catalog: - DESCENDANTS # Optional. Members of any descendant groups will also be considered members of the current group. - SHARED_FROM_GROUPS # Optional. Members of any invited groups will also be considered members of the current group. groupPattern: '[\s\S]*' # Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything - schedule: # Same options as in TaskScheduleDefinition. Optional for the Legacy Backend System. + schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System. # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/notifications/index.md b/docs/notifications/index.md index 95d77a9b7f..86a67ce211 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -54,7 +54,7 @@ backend.add(import('@backstage/plugin-notifications-backend')); ### Add Notifications Frontend ```bash -yarn workspace app add @backstage/notifications +yarn workspace app add @backstage/plugin-notifications ``` To add the notifications main menu, add following to your `packages/app/src/components/Root/Root.tsx`: @@ -109,22 +109,23 @@ Start with: yarn workspace app add @backstage/plugin-signals ``` -To install the plugin, you have to add the following to your `packages/app/src/plugins.ts`: +To install the plugin, add the `SignalsDisplay` to your app root in `packages/app/src/App.tsx`: -```ts -export { signalsPlugin } from '@backstage/plugin-signals'; -``` +```tsx +import { SignalsDisplay } from '@backstage/plugin-signals'; -And make sure that your `packages/app/src/App.tsx` contains: - -```ts -import * as plugins from './plugins'; - -const app = createApp({ - // ... - plugins: Object.values(plugins), - // ... -}); +export default app.createRoot( + <> + + + {/* highlight-add-next-line */} + + + + {routes} + + , +); ``` If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. @@ -218,6 +219,59 @@ React.useEffect(() => { }, [lastSignal, notificationsApi]); ``` +#### Using signals in your own plugin + +It's possible to use signals in your own plugin to deliver data from the backend to the frontend in near real-time. + +To use signals in your own frontend plugin, you need to add the `useSignal` hook from `@backstage/plugin-signals-react` from `@backstage/plugin-notifications-common` with optional generic type of the signal. + +```ts +// To use the same type of signal in the backend, this should be placed in a shared common package +export type MySignalType = { + user: string; + data: string; + // .... +}; + +const { lastSignal } = useSignal('my-plugin'); + +useEffect(() => { + if (lastSignal) { + // Do something with the signal + } +}, [lastSignal]); +``` + +To send signals from the backend plugin, you must add the `signalsServiceRef` to your plugin or module as a dependency. + +```ts +import { signalsServiceRef } from '@backstage/plugin-signals-node'; +export const myPlugin = createBackendPlugin({ + pluginId: 'my', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + signals: signalsServiceRef, + }, + async init({ httpRouter, signals }) { + httpRouter.use( + await createRouter({ + signals, + }), + ); + }, + }); + }, +}); +``` + +To send the signal using the service, you can use the `publish` method. + +```ts +signals.publish({ user: 'user', data: 'test' }); +``` + ### Consuming Notifications In a front-end plugin, the simplest way to query a notification is by its ID: @@ -249,14 +303,18 @@ import { Notification } from '@backstage/plugin-notifications-common'; import { NotificationProcessor } from '@backstage/plugin-notifications-node'; class MyNotificationProcessor implements NotificationProcessor { - async decorate(notification: Notification): Promise { + // preProcess is called before the notification is saved to database. + // This is a good place to modify the notification before it is saved and sent to the user. + async preProcess(notification: Notification): Promise { if (notification.origin === 'plugin-my-plugin') { notification.payload.icon = 'my-icon'; } return notification; } - async send(notification: Notification): Promise { + // postProcess is called after the notification is saved to database and the signal is emitted. + // This is a good place to send the notification to external services. + async postProcess(notification: Notification): Promise { nodemailer.sendEmail({ from: 'backstage', to: 'user', diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index c0e2d61e25..74c055a86c 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -41,7 +41,7 @@ The built-in protection against unauthorized access does not by default include ## Common Backend Configuration -There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected. +There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `SchedulerService` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected. In a typical Backstage setup, there is no boundary between plugins that run on the same host. Likewise, there is no boundary between plugins that share the same database access. Any plugin that is running on a host that has access to the logical database of any other plugin should be considered to have full access to that other plugin. For example, even if you deploy the `auth` and `catalog` plugins on separate hosts with separate configuration and credentials, the `catalog` plugin is still considered to have full access to the `auth` plugin as long as the `catalog` plugin has access to the `auth` plugin's logical database. The only way to create a boundary between the two plugins is to deploy them in such a way that they do not have access to each others’ database. This applies to the database facility as well as any other shared resources, such as the cache. diff --git a/docs/permissions/custom-rules--old.md b/docs/permissions/custom-rules--old.md new file mode 100644 index 0000000000..7661e23327 --- /dev/null +++ b/docs/permissions/custom-rules--old.md @@ -0,0 +1,165 @@ +--- +id: custom-rules--old +title: Defining custom permission rules +description: How to define custom permission rules for existing resources +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./custom-rules.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. + +## Define a custom rule + +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/plugins/permission.ts`. + +We use Zod in our example below. To install, run: + +```bash +yarn workspace backend add zod +``` + +```typescript title="packages/backend/src/plugins/permission.ts" +... + +import type { Entity } from '@backstage/catalog-model'; +import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; +import { createConditionFactory } from '@backstage/plugin-permission-node'; +import { z } from 'zod'; + +export const isInSystemRule = createCatalogPermissionRule({ + name: 'IS_IN_SYSTEM', + description: 'Checks if an entity is part of the system provided', + resourceType: 'catalog-entity', + paramsSchema: z.object({ + systemRef: z + .string() + .describe('SystemRef to check the resource is part of'), + }), + apply: (resource: Entity, { systemRef }) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === 'partOf') + .some(relation => relation.targetRef === systemRef); + }, + toQuery: ({ systemRef }) => ({ + key: 'relations.partOf', + values: [systemRef], + }), +}); + +const isInSystem = createConditionFactory(isInSystemRule); + +... +``` + +For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). + +Still in the `packages/backend/src/plugins/permission.ts` file, let's use the condition we just created in our `TestPermissionPolicy`. + +```ts title="packages/backend/src/plugins/permission.ts" +... +/* highlight-remove-next-line */ +import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; +/* highlight-add-next-line */ +import { catalogConditions, createCatalogConditionalDecision, createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; +/* highlight-remove-next-line */ +import { createConditionFactory } from '@backstage/plugin-permission-node'; +/* highlight-add-next-line */ +import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common'; +/* highlight-add-end */ +... + +export const isInSystemRule = createCatalogPermissionRule({ + name: 'IS_IN_SYSTEM', + description: 'Checks if an entity is part of the system provided', + resourceType: 'catalog-entity', + paramsSchema: z.object({ + systemRef: z + .string() + .describe('SystemRef to check the resource is part of'), + }), + apply: (resource: Entity, { systemRef }) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === 'partOf') + .some(relation => relation.targetRef === systemRef); + }, + toQuery: ({ systemRef }) => ({ + key: 'relations.partOf', + values: [systemRef], + }), +}); + +const isInSystem = createConditionFactory(isInSystemRule); + +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: PolicyQueryUser, + ): Promise { + if (isResourcePermission(request.permission, 'catalog-entity')) { + return createCatalogConditionalDecision( + request.permission, + /* highlight-remove-start */ + catalogConditions.isEntityOwner({ + claims: user?.info.ownershipEntityRefs ?? [], + }), + /* highlight-remove-end */ + /* highlight-add-start */ + { + anyOf: [ + catalogConditions.isEntityOwner({ + claims: user?.info.ownershipEntityRefs ?? [], + }), + isInSystem({ systemRef: 'interviewing' }), + ], + }, + /* highlight-add-end */ + ); + } + + return { result: AuthorizeResult.ALLOW }; + } +} + +... +``` + +## Provide the rule during plugin setup + +Now that we have a custom rule defined and added to our policy, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. + +The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. + +```typescript title="packages/backend/src/plugins/catalog.ts" +import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +/* highlight-add-next-line */ +import { isInSystemRule } from './permission'; + +... + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-next-line */ + builder.addPermissionRules(isInSystemRule); + ... + return router; +} +``` + +The updated policy will allow catalog entity resource permissions if any of the following are true: + +- User owns the target entity +- Target entity is part of the 'interviewing' system diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 57565755fb..9642f80215 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -4,19 +4,23 @@ title: Defining custom permission rules description: How to define custom permission rules for existing resources --- +:::info +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. ## Define a custom rule -Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/plugins/permission.ts`. +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/extensions/permissionsPolicyExtension.ts`. -We use Zod in our example below. To install, run: +We use `zod` and `@backstage/catalog-model` in our example below. To install them run: -```bash -yarn workspace backend add zod +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add zod @backstage/catalog-model ``` -```typescript title="packages/backend/src/plugins/permission.ts" +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" ... import type { Entity } from '@backstage/catalog-model'; @@ -55,9 +59,9 @@ const isInSystem = createConditionFactory(isInSystemRule); For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). -Still in the `packages/backend/src/plugins/permission.ts` file, let's use the condition we just created in our `TestPermissionPolicy`. +Still in the `packages/backend/src/extensions/permissionsPolicyExtension.ts` file, let's use the condition we just created in our `CustomPermissionPolicy`. -```ts title="packages/backend/src/plugins/permission.ts" +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" ... /* highlight-remove-next-line */ import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; @@ -98,7 +102,7 @@ export const isInSystemRule = createCatalogPermissionRule({ const isInSystem = createConditionFactory(isInSystemRule); -class TestPermissionPolicy implements PermissionPolicy { +class CustomPermissionPolicy implements PermissionPolicy { async handle( request: PolicyQuery, user?: PolicyQueryUser, @@ -135,25 +139,49 @@ class TestPermissionPolicy implements PermissionPolicy { Now that we have a custom rule defined and added to our policy, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. -The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. +The api for providing custom rules may differ between plugins, but there should typically be an [extension point](../backend-system/architecture/05-extension-points.md) that you can use in your created module to add your rule. For the catalog, this extension point is exposed via `catalogPermissionExtensionPoint`. Here's the steps you'll need to take to add the `isInSystemRule` we created above to the catalog: -```typescript title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { isInSystemRule } from './permission'; +1. We will be using the `@backstage/plugin-catalog-node` package as it contains the extension point we need. Run this to add it: -... + ```bash title="from your Backstage root directory" + yarn --cwd packages/backend add @backstage/plugin-catalog-node + ``` -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addPermissionRules(isInSystemRule); - ... - return router; -} -``` +2. Next create a `catalogPermissionRules.ts` file in the `packages/backend/src/extensions` folder. +3. Then add this as the contents of the new `catalogPermissionRules.ts` file: + + ```typescript title="packages/backend/src/extensions/catalogPermissionRules.ts" + import { createBackendModule } from '@backstage/backend-plugin-api'; + import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; + import { isInSystemRule } from './permissionPolicyExtension'; + + export default createBackendModule({ + pluginId: 'catalog', + moduleId: 'permission-rules', + register(reg) { + reg.registerInit({ + deps: { catalog: catalogPermissionExtensionPoint }, + async init({ catalog }) { + catalog.addPermissionRules(isInSystemRule); + }, + }); + }, + }); + ``` + +4. Next we need to add this to the backend by adding the following line: + + ```ts title="packages/backend/src/index.ts" + // catalog plugin + backend.add(import('@backstage/plugin-catalog-backend/alpha')); + backend.add( + import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), + ); + /* highlight-add-next-line */ + backend.add(import('./extensions/catalogPermissionRules')); + ``` + +5. Now when you run you Backstage instance - `yarn dev` - the rule will be added to the catalog plugin. The updated policy will allow catalog entity resource permissions if any of the following are true: diff --git a/docs/permissions/getting-started--new.md b/docs/permissions/getting-started--new.md deleted file mode 100644 index b6198a222f..0000000000 --- a/docs/permissions/getting-started--new.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -id: getting-started--new -title: Getting Started -description: How to get started with the permission framework as an integrator ---- - -Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. - -## Prerequisites - -The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. - -### Upgrade to the latest version of Backstage - -To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! - -### Supply an identity resolver to populate group membership on sign in - -**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. - -Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. - -[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. - -## Enable and test the permissions system - -All you need to do now is enable the permissions system in your Backstage instance! - -1. Set the property `permission.enabled` to `true` in `app-config.yaml`. - -```yaml title="app-config.yaml" -permission: - enabled: true -``` - -Congratulations! Now that the framework is configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/getting-started--old.md b/docs/permissions/getting-started--old.md new file mode 100644 index 0000000000..96dbfec92f --- /dev/null +++ b/docs/permissions/getting-started--old.md @@ -0,0 +1,169 @@ +--- +id: getting-started--old +title: Getting Started +description: How to get started with the permission framework as an integrator +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./getting-started.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +If you prefer to watch a video instead, you can start with this video introduction: + + + +:::note Note + +This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +::: + +Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. + +## Prerequisites + +The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. + +### Upgrade to the latest version of Backstage + +The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! + +### Enable service-to-service authentication + +Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. + +To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). + +### Supply an identity resolver to populate group membership on sign in + +**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. + +Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. + +[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. + +## Optionally add cookie-based authentication + +Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. + +## Integrating the permission framework with your Backstage instance + +### 1. Set up the permission backend + +The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: + +1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: + + ```bash title="From your Backstage root directory" + yarn --cwd packages/backend add @backstage/plugin-permission-backend + ``` + +2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. + + ```typescript title="packages/backend/src/plugins/permission.ts" + import { createRouter } from '@backstage/plugin-permission-backend'; + import { + AuthorizeResult, + PolicyDecision, + } from '@backstage/plugin-permission-common'; + import { PermissionPolicy } from '@backstage/plugin-permission-node'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + class TestPermissionPolicy implements PermissionPolicy { + async handle(): Promise { + return { result: AuthorizeResult.ALLOW }; + } + } + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + config: env.config, + logger: env.logger, + discovery: env.discovery, + policy: new TestPermissionPolicy(), + identity: env.identity, + }); + } + ``` + +3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: + + ```ts title="packages/backend/src/index.ts" + import proxy from './plugins/proxy'; + import techdocs from './plugins/techdocs'; + import search from './plugins/search'; + /* highlight-add-next-line */ + import permission from './plugins/permission'; + + async function main() { + const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + /* highlight-add-next-line */ + const permissionEnv = useHotMemoize(module, () => createEnv('permission')); + // .. + + apiRouter.use('/techdocs', await techdocs(techdocsEnv)); + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); + /* highlight-add-next-line */ + apiRouter.use('/permission', await permission(permissionEnv)); + // .. + } + ``` + +### 2. Enable and test the permissions system + +Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. + +1. Set the property `permission.enabled` to `true` in `app-config.yaml`. + + ```yaml title="app-config.yaml" + permission: + enabled: true + ``` + +2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: + + ```ts title="packages/backend/src/plugins/permission.ts" + import { createRouter } from '@backstage/plugin-permission-backend'; + import { + AuthorizeResult, + PolicyDecision, + } from '@backstage/plugin-permission-common'; + /* highlight-remove-next-line */ + import { PermissionPolicy } from '@backstage/plugin-permission-node'; + /* highlight-add-start */ + import { + PermissionPolicy, + PolicyQuery, + } from '@backstage/plugin-permission-node'; + /* highlight-add-end */ + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + class TestPermissionPolicy implements PermissionPolicy { + /* highlight-remove-next-line */ + async handle(): Promise { + /* highlight-add-start */ + async handle(request: PolicyQuery): Promise { + if (request.permission.name === 'catalog.entity.delete') { + return { + result: AuthorizeResult.DENY, + }; + } + /* highlight-add-end */ + + return { result: AuthorizeResult.ALLOW }; + } + } + ``` + +3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. + +![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) + +Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index e8132e571b..d2c487e6ea 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -4,14 +4,8 @@ title: Getting Started description: How to get started with the permission framework as an integrator --- -If you prefer to watch a video instead, you can start with this video introduction: - - - -:::note Note - -This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. - +:::info +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. @@ -22,13 +16,7 @@ The permissions framework depends on a few other Backstage systems, which must b ### Upgrade to the latest version of Backstage -The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! - -### Enable service-to-service authentication - -Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. - -To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). +To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! ### Supply an identity resolver to populate group membership on sign in @@ -38,33 +26,40 @@ Like many other parts of Backstage, the permissions framework relies on informat [The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. -## Optionally add cookie-based authentication +## Test Permission Policy -Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. +To help validate the permission framework is setup we'll create a Test Permission Policy: -## Integrating the permission framework with your Backstage instance +1. Backstage ships with a default Allow All Policy, we want to remove that as it would override our Test Permission Policy. To do this remove the following line: -### 1. Set up the permission backend - -The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: - -1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: - - ```bash title="From your Backstage root directory" - yarn --cwd packages/backend add @backstage/plugin-permission-backend + ```ts title="packages/backend/src/index.ts" + // permission plugin + backend.add(import('@backstage/plugin-permission-backend/alpha')); + /* highlight-remove-start */ + backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), + ); + /* highlight-remove-end */ ``` -2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. +2. Now we need to add the `@backstage/backend-plugin-api` package: - ```typescript title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; + ```bash title="from your Backstage root directory" + yarn --cwd packages/backend add @backstage/backend-plugin-api + ``` + +3. Next we will create an `extensions` folder under `packages/backend/src` +4. In this new `extensions` folder we will add a new file called: `permissionsPolicyExtension.ts` +5. Copy the following into the new `permissionsPolicyExtension.ts` file: + + ```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" + import { createBackendModule } from '@backstage/backend-plugin-api'; import { - AuthorizeResult, PolicyDecision, + AuthorizeResult, } from '@backstage/plugin-permission-common'; import { PermissionPolicy } from '@backstage/plugin-permission-node'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; + import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; class TestPermissionPolicy implements PermissionPolicy { async handle(): Promise { @@ -72,48 +67,34 @@ The permissions framework uses a new `permission-backend` plugin to accept autho } } - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - return await createRouter({ - config: env.config, - logger: env.logger, - discovery: env.discovery, - policy: new TestPermissionPolicy(), - identity: env.identity, - }); - } + export default createBackendModule({ + pluginId: 'permission', + moduleId: 'permission-policy', + register(reg) { + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new TestPermissionPolicy()); + }, + }); + }, + }); ``` -3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: +6. We now need to register this in the backend. We will do this by adding the follow line: ```ts title="packages/backend/src/index.ts" - import proxy from './plugins/proxy'; - import techdocs from './plugins/techdocs'; - import search from './plugins/search'; + // permission plugin + backend.add(import('@backstage/plugin-permission-backend/alpha')); /* highlight-add-next-line */ - import permission from './plugins/permission'; - - async function main() { - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - // .. - - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - /* highlight-add-next-line */ - apiRouter.use('/permission', await permission(permissionEnv)); - // .. - } + backend.add(import('./extensions/permissionPolicyExtension')); ``` -### 2. Enable and test the permissions system +You now have a Test Permission Policy in place, this will help us test that the permission framework is working in the next section. -Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. +## Enable and test the permissions system + +Now lets test end to end that the permissions framework is setup and configured properly we will use the Test Permission Policy we create above as is, then modify it do deny access which will confirm everything is working as expected. Here's how to do that: 1. Set the property `permission.enabled` to `true` in `app-config.yaml`. @@ -122,44 +103,11 @@ Now that the permission backend is running, it’s time to enable the permission enabled: true ``` -2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: +2. Now run `yarn dev`, Backstage should load up in your browser +3. You should see that you have entities in your Catalog, pretty simple +4. Let's change this line in our Test Permission Policy `return { result: AuthorizeResult.ALLOW };` to be `return { result: AuthorizeResult.DENY };` +5. Run `yarn dev` once again, Backstage should load up in your browser +6. This time you should not see any entities in your Catalog, if you do then something went wrong along the way and you'll need to review the steps above +7. Revert the change we made in step 4 so that the line looks like this: `return { result: AuthorizeResult.ALLOW };` - ```ts title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - /* highlight-remove-next-line */ - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - /* highlight-add-start */ - import { - PermissionPolicy, - PolicyQuery, - } from '@backstage/plugin-permission-node'; - /* highlight-add-end */ - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle(request: PolicyQuery): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { result: AuthorizeResult.ALLOW }; - } - } - ``` - -3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. - -![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) - -Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! +Congratulations! Now that the framework is fully configured, you can craft a permission policy that works best for your organization by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/plugin-authors/01-setup--old.md b/docs/permissions/plugin-authors/01-setup--old.md new file mode 100644 index 0000000000..34802d004f --- /dev/null +++ b/docs/permissions/plugin-authors/01-setup--old.md @@ -0,0 +1,134 @@ +--- +id: 01-setup--old +title: 1. Tutorial setup +description: How to get started with the permission framework as a plugin author +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./01-setup.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! + +The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). + +## Setup for the Tutorial + +We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`. + +The source code is available here: + +- [todo-list](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list) +- [todo-list-backend](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend) +- [todo-list-common](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-common) + +1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application: + + ```bash + $ cd $(mktemp -d) + git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git . + git checkout master -- plugins/example-todo-list/ + git checkout master -- plugins/example-todo-list-backend/ + git checkout master -- plugins/example-todo-list-common/ + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json + for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done + cd - + ``` + + The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`. + + **Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above. + +2. Add these packages as dependencies for your Backstage app: + + ```sh title="From your Backstage root directory" + yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common + yarn --cwd packages/app add @internal/plugin-todo-list + ``` + +3. Include the backend and frontend plugin in your application: + + Create a new `packages/backend/src/plugins/todolist.ts` with the following content: + + ```typescript title="packages/backend/src/plugins/todolist.ts" + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { createRouter } from '@internal/plugin-todo-list-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + discovery, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + }); + } + ``` + + Apply the following changes to `packages/backend/src/index.ts`: + + ```ts title="packages/backend/src/index.ts" + import techdocs from './plugins/techdocs'; + /* highlight-add-next-line */ + import todoList from './plugins/todolist'; + import search from './plugins/search'; + + async function main() { + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + /* highlight-add-next-line */ + const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); + // .. + + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); + apiRouter.use('/permission', await permission(permissionEnv)); + /* highlight-add-next-line */ + apiRouter.use('/todolist', await todoList(todoListEnv)); + // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); + // .. + } + ``` + + Apply the following changes to `packages/app/src/App.tsx`: + + ```tsx title="packages/app/src/App.tsx" + /* highlight-add-next-line */ + import { TodoListPage } from '@internal/plugin-todo-list'; + + const routes = ( + + }> + {searchPage} + + } /> + {/* highlight-add-next-line */} + } /> + {/* ... */} + + ); + ``` + +Now if you start your application you should be able to reach the `/todo-list` page: + +![Todo List plugin page](../../assets/permissions/permission-todo-list-page.png) + +--- + +## Integrate the new plugin + +If you play with the UI, you will notice that it is possible to perform a few actions: + +- create a new todo item (`POST /todos`) +- view todo items (`GET /todos`) +- edit an existing todo item (`PUT /todos`) + +Let's try to bring authorization on top of each one of them. diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index f11bdff930..f3c361e8b5 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -4,6 +4,10 @@ title: 1. Tutorial setup description: How to get started with the permission framework as a plugin author --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). @@ -40,58 +44,31 @@ The source code is available here: 2. Add these packages as dependencies for your Backstage app: ```sh title="From your Backstage root directory" - yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common + yarn --cwd packages/backend add @backstage/plugin-permission-backend @backstage/plugin-permission-backend-module-allow-all-policy @internal/plugin-todo-list-backend @internal/plugin-todo-list-common yarn --cwd packages/app add @internal/plugin-todo-list ``` 3. Include the backend and frontend plugin in your application: - Create a new `packages/backend/src/plugins/todolist.ts` with the following content: - - ```typescript title="packages/backend/src/plugins/todolist.ts" - import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - import { createRouter } from '@internal/plugin-todo-list-backend'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - export default async function createPlugin({ - logger, - discovery, - }: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - }); - } - ``` - Apply the following changes to `packages/backend/src/index.ts`: ```ts title="packages/backend/src/index.ts" - import techdocs from './plugins/techdocs'; - /* highlight-add-next-line */ - import todoList from './plugins/todolist'; - import search from './plugins/search'; - - async function main() { - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); - // .. - - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/permission', await permission(permissionEnv)); - /* highlight-add-next-line */ - apiRouter.use('/todolist', await todoList(todoListEnv)); - // Add backends ABOVE this line; this 404 handler is the catch-all fallback - apiRouter.use(notFoundHandler()); - // .. - } + import { createBackend } from '@backstage/backend-defaults'; + //... + const backend = createBackend(); + //... + /* highlight-add-start */ + // Installing the permission plugin + backend.add(import('@backstage/plugin-permission-backend/alpha')); + // Installing the allow all permission policy module + backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), + ); + // Installing the todolist plugin + backend.add(import('@internal/plugin-todo-list-backend')); + /* highlight-add-end */ + //... + backend.start(); ``` Apply the following changes to `packages/app/src/App.tsx`: @@ -99,13 +76,11 @@ The source code is available here: ```tsx title="packages/app/src/App.tsx" /* highlight-add-next-line */ import { TodoListPage } from '@internal/plugin-todo-list'; + //... const routes = ( - }> - {searchPage} - - } /> + {/* ... */} {/* highlight-add-next-line */} } /> {/* ... */} diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md new file mode 100644 index 0000000000..7c8dad3d87 --- /dev/null +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md @@ -0,0 +1,387 @@ +--- +id: 02-adding-a-basic-permission-check--old +title: 2. Adding a basic permission check +description: Explains how to add a basic permission check to a Backstage plugin +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./02-adding-a-basic-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. + +For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). + +We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation. + +## Creating a new permission + +Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission: + +```ts title="plugins/todo-list-common/src/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +/* highlight-remove-start */ +export const tempExamplePermission = createPermission({ + name: 'temp.example.noop', + attributes: {}, +/* highlight-remove-end */ +/* highlight-add-start */ +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +/* highlight-add-end */ +}); + +/* highlight-remove-next-line */ +export const todoListPermissions = [tempExamplePermission]; +/* highlight-add-next-line */ +export const todoListPermissions = [todoListCreatePermission]; +``` + +For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). + +:::note Note + +We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. + +::: + +## Authorizing using the new permission + +Install the following module: + +``` +$ yarn workspace @internal/plugin-todo-list-backend \ + add @backstage/plugin-permission-common @backstage/plugin-permission-node @internal/plugin-todo-list-common +``` + +Edit `plugins/todo-list-backend/src/service/router.ts`: + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-start */ +import { InputError } from '@backstage/errors'; +import { IdentityApi } from '@backstage/plugin-auth-node'; +/* highlight-remove-end */ +/* highlight-add-start */ +import { InputError, NotAllowedError } from '@backstage/errors'; +import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; +import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +export interface RouterOptions { + logger: Logger; + identity: IdentityApi; + /* highlight-add-next-line */ + permissions: PermissionEvaluator; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + /* highlight-remove-next-line */ + const { logger, identity } = options; + /* highlight-add-next-line */ + const { logger, identity, permissions } = options; + + /* highlight-add-start */ + const permissionIntegrationRouter = createPermissionIntegrationRouter({ + permissions: [todoListCreatePermission], + }); + /* highlight-add-end */ + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.json({ status: 'ok' }); + }); + + /* highlight-add-next-line */ + router.use(permissionIntegrationRouter); + + router.get('/todos', async (_req, res) => { + res.json(getAll()); + }); + + router.post('/todos', async (req, res) => { + let author: string | undefined = undefined; + + const user = await identity.getIdentity({ request: req }); + author = user?.identity.userEntityRef; + /* highlight-add-start */ + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); + const decision = ( + await permissions.authorize([{ permission: todoListCreatePermission }], { + token, + }) + )[0]; + + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + /* highlight-add-end */ + + if (!isTodoCreateRequest(req.body)) { + throw new InputError('Invalid payload'); + } + + const todo = add({ title: req.body.title, author }); + res.json(todo); + }); + + // ... +``` + +Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: + +```ts title="packages/backend/src/plugins/todolist.ts" +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +import { createRouter } from '@internal/plugin-todo-list-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + discovery, + /* highlight-add-next-line */ + permissions, +}: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + /* highlight-add-next-line */ + permissions, + }); +} +``` + +That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. + +## Test the authorized create endpoint + +Before running this step, please make sure you followed the steps described in [Getting started](../getting-started.md) section. + +In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: + +```ts title="packages/backend/src/plugins/permission.ts" +import { + PermissionPolicy, + /* highlight-add-start */ + PolicyQuery, + PolicyQueryUser, + /* highlight-add-end */ +} from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { isPermission } from '@backstage/plugin-permission-common'; +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +class TestPermissionPolicy implements PermissionPolicy { + /* highlight-remove-next-line */ + async handle(): Promise { + /* highlight-add-start */ + async handle( + request: PolicyQuery, + _user?: PolicyQueryUser, + ): Promise { + if (isPermission(request.permission, todoListCreatePermission)) { + return { + result: AuthorizeResult.DENY, + }; + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.ALLOW, + }; +} +``` + +Now the frontend should show an error whenever you try to create a new Todo item. + +Let's flip the result back to `ALLOW` before moving on. + +```ts +if (isPermission(request.permission, todoListCreatePermission)) { + return { + /* highlight-remove-next-line */ + result: AuthorizeResult.DENY, + /* highlight-add-next-line */ + result: AuthorizeResult.ALLOW, + }; +} +``` + +At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. + +First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: + +```ts title="plugins/todo-list-backend/src/service/router.test.ts" +import { getVoidLogger } from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +/* highlight-add-next-line */ +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + +/* highlight-add-start */ +const mockedAuthorize: jest.MockedFunction = + jest.fn(); +const mockedPermissionQuery: jest.MockedFunction< + PermissionEvaluator['authorizeConditional'] +> = jest.fn(); + +const permissionEvaluator: PermissionEvaluator = { + authorize: mockedAuthorize, + authorizeConditional: mockedPermissionQuery, +}; +/* highlight-add-end */ + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + identity: {} as DefaultIdentityClient, + /* highlight-add-next-line */ + permissions: permissionEvaluator, + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); +``` + +Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: + +```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" +import { + createServiceBuilder, + loadBackendConfig, + SingleHostDiscovery, + /* highlight-add-next-line */ + ServerTokenManager, +} from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +/* highlight-add-next-line */ +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'todo-list-backend' }); + logger.debug('Starting application server...'); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); + /* highlight-add-start */ + const tokenManager = ServerTokenManager.fromConfig(config, { + logger, + }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + /* highlight-add-end */ + const router = await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + /* highlight-add-next-line */ + permissions, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/todo-list', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); +``` + +Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: + +```ts title="plugins/todo-list-backend/src/plugin.ts" +import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { createRouter } from './service/router'; + +/** +* The example TODO list backend plugin. +* +* @public +*/ +export const exampleTodoListPlugin = createBackendPlugin({ + pluginId: 'exampleTodoList', + register(env) { + env.registerInit({ + deps: { + identity: coreServices.identity, + logger: coreServices.logger, + httpRouter: coreServices.httpRouter, + /* highlight-add-next-line */ + permissions: coreServices.permissions, + }, + /* highlight-remove-next-line */ + async init({ identity, logger, httpRouter }) { + /* highlight-add-next-line */ + async init({ identity, logger, httpRouter, permissions }) { + httpRouter.use( + await createRouter({ + identity, + logger: loggerToWinstonLogger(logger), + permissions, + }), + ); + }, + }); + }, +}); +``` + +Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 724edb7ccb..2d39d5cef3 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -4,6 +4,10 @@ title: 2. Adding a basic permission check description: Explains how to add a basic permission check to a Backstage plugin --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). @@ -57,30 +61,30 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ```ts title="plugins/todo-list-backend/src/service/router.ts" /* highlight-remove-start */ import { InputError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; +import { LoggerService, HttpAuthService } from '@backstage/backend-plugin-api'; /* highlight-remove-end */ /* highlight-add-start */ import { InputError, NotAllowedError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; -import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; +import { LoggerService, HttpAuthService, PermissionsService } from '@backstage/backend-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; /* highlight-add-end */ export interface RouterOptions { - logger: Logger; - identity: IdentityApi; + logger: LoggerService; + httpAuth: HttpAuthService; /* highlight-add-next-line */ - permissions: PermissionEvaluator; + permissions: PermissionsService; } export async function createRouter( options: RouterOptions, ): Promise { /* highlight-remove-next-line */ - const { logger, identity } = options; + const { logger, httpAuth } = options; /* highlight-add-next-line */ - const { logger, identity, permissions } = options; + const { logger, httpAuth, permissions } = options; /* highlight-add-start */ const permissionIntegrationRouter = createPermissionIntegrationRouter({ @@ -109,13 +113,12 @@ export async function createRouter( const user = await identity.getIdentity({ request: req }); author = user?.identity.userEntityRef; /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); const decision = ( - await permissions.authorize([{ permission: todoListCreatePermission }], { - token, - }) + await permissions.authorize( + [{ permission: todoListCreatePermission }], + { credentials }, + ) )[0]; if (decision.result === AuthorizeResult.DENY) { @@ -134,30 +137,43 @@ export async function createRouter( // ... ``` -Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: +Pass the `permissions` object to the plugin in `plugins/todo-list-backend/src/plugin.ts`: -```ts title="packages/backend/src/plugins/todolist.ts" -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { createRouter } from '@internal/plugin-todo-list-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; +```ts title="plugins/todo-list-backend/src/plugin.ts" +import { coreServices, createBackendPlugin } from '@backstage/backend-plugin-api'; +import { createRouter } from './service/router'; -export default async function createPlugin({ - logger, - discovery, - /* highlight-add-next-line */ - permissions, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); -} +export const exampleTodoListPlugin = createBackendPlugin({ + pluginId: 'todolist', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + httpAuth: coreServices.httpAuth, + httpRouter: coreServices.httpRouter, + /* highlight-add-next-line */ + permissions: coreServices.permissions, + }, + /* highlight-remove-next-line */ + async init({ logger, httpAuth, httpRouter }) { + /* highlight-add-next-line */ + async init({ logger, httpAuth, httpRouter, permissions }) { + httpRouter.use( + await createRouter({ + logger, + httpAuth, + /* highlight-add-next-line */ + permissions, + }), + ); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + }, + }); + }, +}); ``` That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. @@ -168,7 +184,15 @@ Before running this step, please make sure you followed the steps described in [ In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: -```ts title="packages/backend/src/plugins/permission.ts" +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + PolicyDecision, + /* highlight-add-start */ + isPermission, + /* highlight-add-end */ + AuthorizeResult, +} from '@backstage/plugin-permission-common'; import { PermissionPolicy, /* highlight-add-start */ @@ -177,9 +201,9 @@ import { /* highlight-add-end */ } from '@backstage/plugin-permission-node'; /* highlight-add-start */ -import { isPermission } from '@backstage/plugin-permission-common'; import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; /* highlight-add-end */ +import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; class TestPermissionPolicy implements PermissionPolicy { /* highlight-remove-next-line */ @@ -200,6 +224,19 @@ class TestPermissionPolicy implements PermissionPolicy { result: AuthorizeResult.ALLOW, }; } + +export default createBackendModule({ + pluginId: 'permission', + moduleId: 'permission-policy', + register(reg) { + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new TestPermissionPolicy()); + }, + }); + }, +}); ``` Now the frontend should show an error whenever you try to create a new Todo item. @@ -217,42 +254,26 @@ if (isPermission(request.permission, todoListCreatePermission)) { } ``` -At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. +At this point everything is working but if you run `yarn tsc` you'll get an error, let's fix this up. -First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: +Clean up the `plugins/todo-list-backend/src/service/router.test.ts`: ```ts title="plugins/todo-list-backend/src/service/router.test.ts" -import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; - -/* highlight-add-start */ -const mockedAuthorize: jest.MockedFunction = - jest.fn(); -const mockedPermissionQuery: jest.MockedFunction< - PermissionEvaluator['authorizeConditional'] -> = jest.fn(); - -const permissionEvaluator: PermissionEvaluator = { - authorize: mockedAuthorize, - authorizeConditional: mockedPermissionQuery, -}; -/* highlight-add-end */ +import { mockServices } from '@backstage/backend-test-utils'; describe('createRouter', () => { let app: express.Express; beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), - identity: {} as DefaultIdentityClient, + logger: mockServices.logger.mock(), + httpAuth: mockServices.httpAuth.mock(), /* highlight-add-next-line */ - permissions: permissionEvaluator, + permissions: mockServices.permissions.mock(), }); app = express().use(router); }); @@ -272,112 +293,4 @@ describe('createRouter', () => { }); ``` -Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: - -```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" -import { - createServiceBuilder, - loadBackendConfig, - SingleHostDiscovery, - /* highlight-add-next-line */ - ServerTokenManager, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = SingleHostDiscovery.fromConfig(config); - /* highlight-add-start */ - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - /* highlight-add-end */ - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); -``` - -Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: - -```ts title="plugins/todo-list-backend/src/plugin.ts" -import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; - -/** -* The example TODO list backend plugin. -* -* @public -*/ -export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', - register(env) { - env.registerInit({ - deps: { - identity: coreServices.identity, - logger: coreServices.logger, - httpRouter: coreServices.httpRouter, - /* highlight-add-next-line */ - permissions: coreServices.permissions, - }, - /* highlight-remove-next-line */ - async init({ identity, logger, httpRouter }) { - /* highlight-add-next-line */ - async init({ identity, logger, httpRouter, permissions }) { - httpRouter.use( - await createRouter({ - identity, - logger: loggerToWinstonLogger(logger), - permissions, - }), - ); - }, - }); - }, -}); -``` - Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md new file mode 100644 index 0000000000..ee06629e71 --- /dev/null +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md @@ -0,0 +1,299 @@ +--- +id: 03-adding-a-resource-permission-check--old +title: 3. Adding a resource permission check +description: Explains how to add a resource permission check to a Backstage plugin +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./03-adding-a-resource-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. + +## Creating the update permission + +Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md). + +```ts title="plugins/todo-list-common/src/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +/* highlight-add-next-line */ +export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; + +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +}); + +/* highlight-add-start */ +export const todoListUpdatePermission = createPermission({ + name: 'todo.list.update', + attributes: { action: 'update' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); +/* highlight-add-end */ + +/* highlight-remove-next-line */ +export const todoListPermissions = [todoListCreatePermission]; +/* highlight-add-start */ +export const todoListPermissions = [ + todoListCreatePermission, + todoListUpdatePermission, +]; +/* highlight-add-end */ +``` + +Notice that unlike `todoListCreatePermission`, the `todoListUpdatePermission` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource. + +## Setting up authorization for the update permission + +To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section: + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-next-line */ +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-start */ +import { + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +// ... + +const permissionIntegrationRouter = createPermissionIntegrationRouter({ + /* highlight-remove-next-line */ + permissions: [todoListCreatePermission], + /* highlight-add-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission], +}); + +// ... + +router.put('/todos', async (req, res) => { + /* highlight-add-start */ + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); + /* highlight-add-end */ + + if (!isTodoUpdateRequest(req.body)) { + throw new InputError('Invalid payload'); + } + /* highlight-add-start */ + const decision = ( + await permissions.authorize( + [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], + { + token, + }, + ) + )[0]; + + if (decision.result !== AuthorizeResult.ALLOW) { + throw new NotAllowedError('Unauthorized'); + } + /* highlight-add-end */ + + res.json(update(req.body)); +}); +``` + +**Important:** Notice that we are passing an extra `resourceRef` field, with the `id` of the todo item as the value. + +This enables decisions based on characteristics of the resource, but it's important to note that policy authors will not have access to the resource ref inside of their permission policies. Instead, the policies will return conditional decisions, which we need to now support in our plugin. + +## Adding support for conditional decisions + +Install the missing module: + +```bash +$ yarn workspace @internal/plugin-todo-list-backend add zod +``` + +Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: + +```typescript title="plugins/todo-list-backend/src/service/rules.ts" +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { z } from 'zod'; +import { Todo, TodoFilter } from './todos'; + +export const createTodoListPermissionRule = makeCreatePermissionRule< + Todo, + TodoFilter, + typeof TODO_LIST_RESOURCE_TYPE +>(); + +export const isOwner = createTodoListPermissionRule({ + name: 'IS_OWNER', + description: 'Should allow only if the todo belongs to the user', + resourceType: TODO_LIST_RESOURCE_TYPE, + paramsSchema: z.object({ + userId: z.string().describe('User ID to match on the resource'), + }), + apply: (resource: Todo, { userId }) => { + return resource.author === userId; + }, + toQuery: ({ userId }) => { + return { + property: 'author', + values: [userId], + }; + }, +}); + +export const rules = { isOwner }; +``` + +`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. + +:::note Note + +To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. + +::: + +We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. +Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. + +Let's skip the `toQuery` function for now, we'll come back to that in the next section. + +Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/service/router.ts`. This uses the `createPermissionIntegrationRouter` helper to add the APIs needed by the permission framework to your plugin. You'll need to supply: + +- `getResources`: a function that accepts an array of `resourceRefs` in the same format you expect to be passed to `authorize`, and returns an array of the corresponding resources. +- `resourceType`: the same value used in the permission rule above. +- `permissions`: the list of permissions that your plugin accepts. +- `rules`: an array of all the permission rules you want to support in conditional decisions. + +```ts title="plugins/todo-list-backend/src/service/router.ts" +// ... +import { + /* highlight-add-next-line */ + TODO_LIST_RESOURCE_TYPE, + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +/* highlight-remove-next-line */ +import { add, getAll, update } from './todos'; +/* highlight-add-start */ +import { add, getAll, getTodo, update } from './todos'; +import { rules } from './rules'; +/* highlight-add-end */ + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger, identity, permissions } = options; + + const permissionIntegrationRouter = createPermissionIntegrationRouter({ + permissions: [todoListCreatePermission, todoListUpdatePermission], + /* highlight-add-start */ + getResources: async resourceRefs => { + return resourceRefs.map(getTodo); + }, + resourceType: TODO_LIST_RESOURCE_TYPE, + rules: Object.values(rules), + /* highlight-add-end */ + }); + + const router = Router(); + router.use(express.json()); + + // ... +} +``` + +## Provide utilities for policy authors + +Now that we have a new resource type and a corresponding rule, we need to export some utilities for policy authors to reference them. + +Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add the following code: + +```typescript title="plugins/todo-list-backend/src/conditionExports.ts" +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { createConditionExports } from '@backstage/plugin-permission-node'; +import { rules } from './service/rules'; + +const { conditions, createConditionalDecision } = createConditionExports({ + pluginId: 'todolist', + resourceType: TODO_LIST_RESOURCE_TYPE, + rules, +}); + +export const todoListConditions = conditions; + +export const createTodoListConditionalDecision = createConditionalDecision; +``` + +Make sure `todoListConditions` and `createTodoListConditionalDecision` are exported from the `todo-list-backend` package by editing `plugins/todo-list-backend/src/index.ts`: + +```ts title="plugins/todo-list-backend/src/index.ts" +export * from './service/router'; +/* highlight-add-next-line */ +export * from './conditionExports'; +export { exampleTodoListPlugin } from './plugin'; +``` + +## Test the authorized update endpoint + +Let's go back to the permission policy's handle function and try to authorize our new permission with an `isOwner` condition. + +```ts title="packages/backend/src/plugins/permission.ts" +import { + IdentityClient +} from '@backstage/plugin-auth-node'; +import { + PermissionPolicy, + PolicyQuery, + PolicyQueryUser, +} from '@backstage/plugin-permission-node'; +import { isPermission } from '@backstage/plugin-permission-common'; +/* highlight-remove-next-line */ +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-start */ +import { + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +import { + todoListConditions, + createTodoListConditionalDecision, +} from '@internal/plugin-todo-list-backend'; +/* highlight-add-end */ + + +async handle( + request: PolicyQuery, + /* highlight-remove-next-line */ + _user?: PolicyQueryUser, + /* highlight-add-next-line */ + user?: PolicyQueryUser, +): Promise { + if (isPermission(request.permission, todoListCreatePermission)) { + return { + result: AuthorizeResult.ALLOW, + }; + } + /* highlight-add-start */ + if (isPermission(request.permission, todoListUpdatePermission)) { + return createTodoListConditionalDecision( + request.permission, + todoListConditions.isOwner({ + userId: user?.info.userEntityRef ?? '', + }), + ); + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.ALLOW, + }; +} +``` + +For any incoming update requests, we now return a _Conditional Decision_. We are saying: + +> Hey permission framework, I can't make a decision alone. Please go to the plugin with id `todolist` and ask it to apply these conditions. + +To check that everything works as expected, you should now see an error in the UI whenever you try to edit an item that wasn’t created by you. Success! diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 7ad551e926..3f5a9777b3 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -4,6 +4,10 @@ title: 3. Adding a resource permission check description: Explains how to add a resource permission check to a Backstage plugin --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. ## Creating the update permission @@ -68,9 +72,7 @@ const permissionIntegrationRouter = createPermissionIntegrationRouter({ router.put('/todos', async (req, res) => { /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); /* highlight-add-end */ if (!isTodoUpdateRequest(req.body)) { @@ -80,9 +82,7 @@ router.put('/todos', async (req, res) => { const decision = ( await permissions.authorize( [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], - { - token, - }, + { credentials }, ) )[0]; @@ -146,7 +146,7 @@ export const rules = { isOwner }; :::note Note -To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. +To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `extension point`. ::: diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md new file mode 100644 index 0000000000..cca6899ef6 --- /dev/null +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md @@ -0,0 +1,193 @@ +--- +id: 04-authorizing-access-to-paginated-data--old +title: 4. Authorizing access to paginated data +description: Explains how to authorize access to paginated data in a Backstage plugin +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. + +One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: + +```ts +router.get('/todos', async (req, res) => { + /* highlight-add-next-line */ + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); + + /* highlight-remove-next-line */ + res.json(getAll()); + /* highlight-add-start */ + const items = getAll(); + const decisions = await permissions.authorize( + items.map(({ id }) => ({ + permission: todoListReadPermission, + resourceRef: id, + })), + { credentials }, + ); + + const filteredItems = decisions.filter( + decision => decision.result === AuthorizeResult.ALLOW, + ); + res.json(filteredItems); + /* highlight-add-end */ +}); +``` + +This approach will work for simple cases, but it has a downside: it forces us to retrieve all the elements upfront and authorize them one by one. This forces the plugin implementation to handle concerns like pagination, which is currently handled by the data source. + +To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. + +:::note Note + +In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. + +::: + +## Creating the read permission + +Let's add another permission to the plugin. + +```ts title="plugins/todo-list-backend/src/service/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; + +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +}); + +export const todoListUpdatePermission = createPermission({ + name: 'todo.list.update', + attributes: { action: 'update' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); + +/* highlight-add-start */ +export const todoListReadPermission = createPermission({ + name: 'todos.list.read', + attributes: { action: 'read' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); +/* highlight-add-end */ + +export const todoListPermissions = [ + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-start */ + todoListReadPermission, + /* highlight-add-end */ +]; +``` + +## Using conditional policy decisions + +So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-next-line */ +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { + createPermissionIntegrationRouter, + createConditionTransformer, + ConditionTransformer, +} from '@backstage/plugin-permission-node'; +/* highlight-add-end */ +/* highlight-remove-next-line */ +import { add, getAll, getTodo, update } from './todos'; +/* highlight-add-next-line */ +import { add, getAll, getTodo, TodoFilter, update } from './todos'; +import { + TODO_LIST_RESOURCE_TYPE, + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-next-line */ + todoListReadPermission, +} from './permissions'; + +// ... + +const permissionIntegrationRouter = createPermissionIntegrationRouter({ + /* highlight-remove-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission], + /* highlight-add-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission, todoListReadPermission], + getResources: async resourceRefs => { + return resourceRefs.map(getTodo); + }, + resourceType: TODO_LIST_RESOURCE_TYPE, + rules: Object.values(rules), +}); + +// ... + +/* highlight-add-next-line */ +const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); + +/* highlight-remove-next-line */ +router.get('/todos', async (_req, res) => { +/* highlight-add-start */ +router.get('/todos', async (req, res) => { + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); + + const decision = ( + await permissions.authorizeConditional([{ permission: todoListReadPermission }], { + credentials, + }) + )[0]; + + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + if (decision.result === AuthorizeResult.CONDITIONAL) { + const filter = transformConditions(decision.conditions); + res.json(getAll(filter)); + } else { + res.json(getAll()); + } +/* highlight-add-end */ + /* highlight-remove-next-line */ + res.json(getAll()); +}); +``` + +To make the process of handling conditional decisions easier, the permission framework provides a `createConditionTransformer` helper. This function accepts an array of permission rules, and returns a transformer function which converts the conditions to the format needed by the plugin using the `toQuery` method defined on each rule. + +Since `TodoFilter` used in our plugin matches the structure of the conditions object, we can directly pass the output of our condition transformer. If the filters were structured differently, we'd need to transform it further before passing it to the api. + +## Test the authorized read endpoint + +Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. + +```ts title="packages/backend/src/plugins/permission.ts" +import { + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-next-line */ + todoListReadPermission, +} from '@internal/plugin-todo-list-common'; + +/* highlight-remove-next-line */ +if (isPermission(request.permission, todoListUpdatePermission)) { +/* highlight-add-start */ +if ( + isPermission(request.permission, todoListUpdatePermission) || + isPermission(request.permission, todoListReadPermission) +) { +/* highlight-add-end */ + return createTodoListConditionalDecision( + request.permission, + todoListConditions.isOwner({ + userId: user?.identity.userEntityRef + }), + ); +} +``` + +Once the changes to the permission policy are saved, the UI should show only the todo items you've created. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index 84e86140fc..883884b6e5 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -4,6 +4,8 @@ title: 4. Authorizing access to paginated data description: Explains how to authorize access to paginated data in a Backstage plugin --- +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! + Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: @@ -11,7 +13,7 @@ One possible solution may leverage the batching functionality to authorize all o ```ts router.get('/todos', async (req, res) => { /* highlight-add-next-line */ - const token = IdentityClient.getBearerToken(req.header('authorization')); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); /* highlight-remove-next-line */ res.json(getAll()); @@ -22,6 +24,7 @@ router.get('/todos', async (req, res) => { permission: todoListReadPermission, resourceRef: id, })), + { credentials }, ); const filteredItems = decisions.filter( @@ -81,7 +84,7 @@ export const todoListPermissions = [ ## Using conditional policy decisions -So far we've only used the `PermissionEvaluator.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionEvaluator.authorizeConditional` instead. +So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. ```ts title="plugins/todo-list-backend/src/service/router.ts" /* highlight-remove-next-line */ @@ -128,13 +131,11 @@ const transformConditions: ConditionTransformer = createConditionTra router.get('/todos', async (_req, res) => { /* highlight-add-start */ router.get('/todos', async (req, res) => { - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); const decision = ( await permissions.authorizeConditional([{ permission: todoListReadPermission }], { - token, + credentials, }) )[0]; diff --git a/docs/permissions/writing-a-policy--old.md b/docs/permissions/writing-a-policy--old.md new file mode 100644 index 0000000000..d95fa7ffb3 --- /dev/null +++ b/docs/permissions/writing-a-policy--old.md @@ -0,0 +1,148 @@ +--- +id: writing-a-policy--old +title: Writing a permission policy +description: How to write your own permission policy as a Backstage integrator +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./writing-a-policy.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. + +That policy looked like this: + +```typescript title="packages/backend/src/plugins/permission.ts" +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + _user?: PolicyQueryUser, + ): Promise { + if (request.permission.name === 'catalog.entity.delete') { + return { + result: AuthorizeResult.DENY, + }; + } + + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +## What's in a policy? + +Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). + +In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.definitivepolicydecision) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). + +As we confirmed in the previous section, we know that this now prevents us from unregistering catalog components. Hooray! But you may notice that this prevents _anyone_ from unregistering a component, which is not a very realistic policy. Let's improve this policy by disabling the unregister action _unless you are the owner of this component_. + +## Conditional decisions + +Let's change the policy to the following: + +```ts +import { + AuthorizeResult, + PolicyDecision, + /* highlight-add-next-line */ + isPermission, +} from '@backstage/plugin-permission-common'; +/* highlight-add-start */ +import { + catalogConditions, + createCatalogConditionalDecision, +} from '@backstage/plugin-catalog-backend/alpha'; +import { + catalogEntityDeletePermission, +} from '@backstage/plugin-catalog-common/alpha'; +/* highlight-add-end */ + +class TestPermissionPolicy implements PermissionPolicy { + /* highlight-remove-next-line */ + async handle(request: PolicyQuery): Promise { + /* highlight-add-start */ + async handle( + request: PolicyQuery, + user?: PolicyQueryUser, + ): Promise { + /* highlight-add-end */ + /* highlight-remove-next-line */ + if (request.permission.name === 'catalog.entity.delete') { + /* highlight-add-next-line */ + if (isPermission(request.permission, catalogEntityDeletePermission)) { + /* highlight-remove-start */ + return { + result: AuthorizeResult.DENY, + }; + /* highlight-remove-end */ + /* highlight-add-start */ + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner({ + claims: user?.info.ownershipEntityRefs ?? [], + }), + ); + /* highlight-add-end */ + } + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +Let's walk through the new code that we just added. + +Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. + +The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. + +You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities! + +## Resource types + +Now let's say we want to prevent all actions on catalog entities unless performed by the owner. One way to achieve this may be to simply update the `if` statement and check for each permission. If you choose to write your policy this way, it will certainly work! However, it may be difficult to maintain as the policy grows, and it may not be obvious if certain permissions are left out. We can author this same policy in a more scalable way by checking the resource type of the requested permission. + +```ts +import { + AuthorizeResult, + PolicyDecision, + /* highlight-remove-next-line */ + isPermission, + isResourcePermission, + /* highlight-add-next-line */ +} from '@backstage/plugin-permission-common'; +import { + catalogConditions, + createCatalogConditionalDecision, +} from '@backstage/plugin-catalog-backend/alpha'; +/* highlight-remove-start */ +import { + catalogEntityDeletePermission, +} from '@backstage/plugin-catalog-common/alpha'; +/* highlight-remove-end */ + +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: PolicyQueryUser, + ): Promise { + /* highlight-remove-next-line */ + if (isPermission(request.permission, catalogEntityDeletePermission)) { + /* highlight-add-next-line */ + if (isResourcePermission(request.permission, 'catalog-entity')) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner({ + claims: user?.info.ownershipEntityRefs ?? [], + }), + ); + } + + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! + +_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index 0f2d4fe091..ce662b6e1a 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -4,16 +4,40 @@ title: Writing a permission policy description: How to write your own permission policy as a Backstage integrator --- +:::info +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. That policy looked like this: -```typescript title="packages/backend/src/plugins/permission.ts" +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - _user?: PolicyQueryUser, - ): Promise { + async handle(): Promise { + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +That is a very simple example and it's not really doing anything helpful, let's expand this a little more. + +First, let's rename this from `TestPermissionPolicy` to `CustomPermissionPolicy` as you'll build on adding to it as your permissions needs require. Then we'll add a check for a permission. Here's what the full `permissionsPolicyExtension.ts` will look like: + +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + PolicyDecision, + AuthorizeResult, +} from '@backstage/plugin-permission-common'; +import { + PermissionPolicy, + PolicyQuery, +} from '@backstage/plugin-permission-node'; +import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; + +class CustomPermissionPolicy implements PermissionPolicy { + async handle(request: PolicyQuery): Promise { if (request.permission.name === 'catalog.entity.delete') { return { result: AuthorizeResult.DENY, @@ -23,8 +47,23 @@ class TestPermissionPolicy implements PermissionPolicy { return { result: AuthorizeResult.ALLOW }; } } + +export default createBackendModule({ + pluginId: 'permission', + moduleId: 'permission-policy', + register(reg) { + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new CustomPermissionPolicy()); + }, + }); + }, +}); ``` +Now with this policy in place the ability to delete entities in the Catalog is not allowed for anyone. The following sections will expand on the concepts used here. + ## What's in a policy? Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). @@ -54,7 +93,7 @@ import { } from '@backstage/plugin-catalog-common/alpha'; /* highlight-add-end */ -class TestPermissionPolicy implements PermissionPolicy { +class CustomPermissionPolicy implements PermissionPolicy { /* highlight-remove-next-line */ async handle(request: PolicyQuery): Promise { /* highlight-add-start */ @@ -117,7 +156,7 @@ import { } from '@backstage/plugin-catalog-common/alpha'; /* highlight-remove-end */ -class TestPermissionPolicy implements PermissionPolicy { +class CustomPermissionPolicy implements PermissionPolicy { async handle( request: PolicyQuery, user?: PolicyQueryUser, @@ -141,4 +180,8 @@ class TestPermissionPolicy implements PermissionPolicy { In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! -_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. +:::note Note + +Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. + +::: diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 1a9c0babf9..a0e0e2d5e6 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -329,7 +329,7 @@ You can also use static configuration to bind routes, removing the need to make changes to the app code. It does however mean that you won't get type safety when binding routes and compile-time validation of the bindings. Static configuration of route bindings is done under the `app.routes.bindings` key in -`app-config.yaml`. It works the same way as [route bindings in the new frontend system](../frontend-system/architecture/07-routes.md#binding-external-route-references), +`app-config.yaml`. It works the same way as [route bindings in the new frontend system](../frontend-system/architecture/36-routes.md#binding-external-route-references), for example: ```yaml @@ -342,7 +342,7 @@ app: ### Default Targets for External Route References Following the `1.28` release of Backstage you can now define default targets for -external route references. They work the same way as [default targets in the new frontend system](../frontend-system/architecture/07-routes.md#default-targets-for-external-route-references), +external route references. They work the same way as [default targets in the new frontend system](../frontend-system/architecture/36-routes.md#default-targets-for-external-route-references), for example: ```ts diff --git a/docs/plugins/integrating-search-into-plugins--old.md b/docs/plugins/integrating-search-into-plugins--old.md new file mode 100644 index 0000000000..2729dd3d21 --- /dev/null +++ b/docs/plugins/integrating-search-into-plugins--old.md @@ -0,0 +1,421 @@ +--- +id: integrating-search-into-plugins--old +title: Integrating Search into a plugin +description: How to integrate Search into a Backstage plugin +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./integrating-search-into-plugins.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage Search Platform was designed to give plugin developers the APIs +and interfaces needed to offer search experiences within their plugins, while +abstracting away (and instead empowering application integrators to choose) the +specific underlying search technologies. + +On this page, you'll find concepts and tutorials for leveraging the Backstage +Search Platform in your plugin. + +## Providing data to the search platform + +### Create a collator + +> Knowing what a [collator](../features/search/concepts.md#collators) is will help you as you build it out. + +Imagine you have a plugin that is responsible for storing FAQ snippets in a database. You want other engineers to be able to easily find your questions and answers. So that means you want them to be indexed by the search platform. Lets say the FAQ snippets can be viewed at a URL like `backstage.example.biz/faq-snippets`. + +The search platform provides an interface (`DocumentCollatorFactory` from package `@backstage/plugin-search-common`) that allows you to do exactly that. It works by registering each of your entries as a "document" that later represents one search result each. + +> You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. + +#### 1. Install collator interface dependencies + +We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: + +```sh +# navigate to the plugin directory +# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) +cd plugins/faq-snippets + +# Create a new branch using Git command-line +git checkout -b tutorials/new-faq-snippets-collator + +# Install the package containing the interface +yarn add @backstage/plugin-search-common +``` + +#### 2. Define your document type + +Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. + +Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: + +```ts +import { IndexableDocument } from '@backstage/plugin-search-common'; + +export interface FaqSnippetDocument extends IndexableDocument { + answered_by: string; +} +``` + +#### 3. Use Backstage App configuration + +Your new collator could benefit from using configuration directly from the Backstage `app-config.yaml` file which is located on the project's root folder: + +```yaml +faq: + baseUrl: https://backstage.example.biz/faq-snippets +``` + +#### 4. Implement your collator + +Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: + +```json +{ + "items": [ + { + "id": 42, + "question": "What is The Answer to the Ultimate Question of Life, the Universe, and Everything?", + "answer": "Forty-two", + "user": "Deep Thought" + } + ] +} +``` + +Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: + +```ts +import fetch from 'cross-fetch'; +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; +import { Readable } from 'stream'; +import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; + +import { FaqDocument } from './FaqDocument'; + +export type FaqCollatorFactoryOptions = { + baseUrl?: string; + logger: Logger; +}; + +export class FaqCollatorFactory implements DocumentCollatorFactory { + private readonly baseUrl: string | undefined; + private readonly logger: Logger; + public readonly type: string = 'faq-snippets'; + + private constructor(options: FaqCollatorFactoryOptions) { + this.baseUrl = options.baseUrl; + this.logger = options.logger; + } + + static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { + const baseUrl = + config.getOptionalString('faq.baseUrl') || + 'https://backstage.example.biz/faq-snippets'; + return new FaqCollatorFactory({ ...options, baseUrl }); + } + + async getCollator() { + return Readable.from(this.execute()); + } + + async *execute(): AsyncGenerator { + if (!this.baseUrl) { + this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); + return; + } + + const response = await fetch(this.baseUrl); + const data = await response.json(); + + for (const faq of data.items) { + yield { + title: faq.question, + location: `/faq-snippets/${faq.id}`, + text: faq.answer, + answered_by: faq.user, + }; + } + } +} +``` + +#### 5. Test your collator + +To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/docs/reference/plugin-search-backend-node.testpipeline) utility that emulates a pipeline into which you can integrate your custom collator. + +Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. + +#### 6. Make your plugins collator discoverable for others + +If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). + +## Building a search experience into your plugin + +While the core Search plugin offers components and extensions that empower app +integrators to compose a global search experience, you may find that you want a +narrower search experience just within your plugin. This could be as literal as +an autocomplete-style search bar focused on documents provided by your plugin +(for example, the [TechDocsSearch](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/search/components/TechDocsSearch.tsx) +component), or as abstract as a widget that presents a list of links that +are contextually related to something else on the page. + +### Search Experience Concepts + +Knowing these high-level concepts will help you as you craft your in-plugin +search experience. + +- All search experiences must be wrapped in a ``, which + is provided by `@backstage/plugin-search-react`. This context keeps track + of state necessary to perform search queries and display any results. As + inputs to the query are updated (e.g. a `term` or `filter` values), the + updated query is executed and `results` are refreshed. Check out the + [SearchContextValue](https://backstage.io/docs/reference/plugin-search-react.searchcontextvalue) + for details. +- The aforementioned state can be modified and/or consumed via the + `useSearch()` hook, also exported by `@backstage/plugin-search-react`. +- For more literal search experiences, reusable components are available + to import and compose into a cohesive experience in your plugin (e.g. + `` or ``). You can see all such + components in [Backstage's storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchbar--default). + +### Search Experience Tutorials + +The following tutorials make use of packages and plugins that you may not yet +have as dependencies for your plugin; be sure to add them before you use them! + +- [`@backstage/plugin-search-react`](https://www.npmjs.com/package/@backstage/plugin-search-react) - A + package containing components, hooks, and types that are shared across all + frontend plugins, including plugins like yours! +- [`@backstage/plugin-search`](https://www.npmjs.com/package/@backstage/plugin-search) - The + main search plugin, used by app integrators to compose global search + experiences. +- [`@backstage/core-components`](https://www.npmjs.com/package/@backstage/core-components) - A + package containing generic components useful for a variety of experiences + built in Backstage. + +#### Improved "404" page experience + +Imagine you have a plugin that allows users to manage _widgets_. Perhaps they +can be viewed at a URL like `backstage.example.biz/widgets/{widgetName}`. +At some point, a widget is renamed, and links to that widget's page from +chat systems, wikis, or browser bookmarks become stale, resulting in errors or +404s. + +What if instead of showing a broken page or the generic "looks like someone +dropped the mic" 404 page, you showed a list of possibly related widgets? + +```javascript +import { Link } from '@backstage/core-components'; +import { SearchResult } from '@backstage/plugin-search'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; + +export const Widget404Page = ({ widgetName }) => { + // Supplying this to runs a pre-filtered search with + // the given widgetName as the search term, focused on search result of type + // "widget" with no other filters. + const preFiltered = { + term: widgetName, + types: ['widget'], + filters: {}, + }; + + return ( + + {/* The component allows us to iterate through results and + display them in whatever way fits best! */} + + {({ results }) => ( + {results.map(({ document }) => ( + + {document.title} + + ))} + )} + + + ); +); +``` + +Not all search experiences require user input! As you can see, it's possible to +leverage the Backstage Search Platform's frontend framework without necessarily +giving users input controls. + +#### Simple search page + +Of course, it's also possible to provide a more fully featured search +experience in your plugin. The simplest way is to leverage reusable components +provided by the `@backstage/plugin-search` package, like this: + +```javascript +import { useProfile } from '@internal/api'; +import { + Content, + ContentHeader, + PageWithHeader, +} from '@backstage/core-components'; +import { SearchBar, SearchResult } from '@backstage/plugin-search'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; + +export const ManageMyWidgets = () => { + const { primaryTeam } = useProfile(); + // In this example, note how we are pre-filtering results down to a specific + // owner field value (the currently logged-in user's team), but allowing the + // search term to be controlled by the user via the component. + const preFiltered = { + types: ['widget'], + term: '', + filters: { + owner: primaryTeam, + }, + }; + + return ( + + + + + + + {/* Render results here, just like above */} + + + + + ); +}; +``` + +#### Custom search control surfaces + +If the reusable search components provided by `@backstage/plugin-search` aren't +adequate, no problem! There's an API in place that you can use to author your +own components to control the various parts of the search context. + +```javascript +import { useSearch } from '@backstage/plugin-search-react'; +import ChipInput from 'material-ui-chip-input'; + +export const CustomChipFilter = ({ name }) => { + const { filters, setFilters } = useSearch(); + const chipValues = filters[name] || []; + + // When a chip value is changed, update the filters value by calling the + // setFilters function from the search context. + const handleChipChange = (chip, index) => { + // There may be filters set for other fields. Be sure to maintain them. + setFilters(prevState => { + const { [name]: filter = [], ...others } = prevState; + + if (index === undefined) { + filter.push(chip); + } else { + filter.splice(index, 1); + } + + return { ...others, [name]: filter }; + }); + }; + + return ( + + ); +}; +``` + +Check out the [SearchContextValue type](https://github.com/backstage/backstage/blob/master/plugins/search-react/src/context/SearchContext.tsx) +for more details on what methods and values are available for manipulating and +reading the search context. + +If you produce something generic and reusable, consider contributing your +component upstream so that all users of the Backstage Search Platform can +benefit. Issues and pull requests welcome. + +#### Custom search results + +Search results throughout Backstage are rendered as lists so that list items can easily be customized; although a [default result list item](https://backstage.io/storybook/?path=/story/plugins-search-defaultresultlistitem--default) is available, plugins are in the best position to provide custom result list items that surface relevant information only known to the plugin. + +The example below imagines `YourCustomSearchResult` as a type of search result that contains associated `tags` which could be rendered as chips below the title/text. + +```tsx +import { Link } from '@backstage/core-components'; +import { useAnalytics } from '@backstage/core-plugin-api'; +import { ResultHighlight } from '@backstage/plugin-search-common'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; + +type CustomSearchResultListItemProps = { + result: YourCustomSearchResult; + rank?: number; + highlight?: ResultHighlight; +}; + +export const CustomSearchResultListItem = ( + props: CustomSearchResultListItemProps, +) => { + const { title, text, location, tags } = props.result; + + const analytics = useAnalytics(); + const handleClick = () => { + analytics.captureEvent('discover', title, { + attributes: { to: location }, + value: props.rank, + }); + }; + + return ( + + + + + ) : ( + title + ) + } + secondary={ + highlight?.fields?.text ? ( + + ) : ( + text + ) + } + /> + {tags && + tags.map((tag: string) => ( + + ))} + + + + + ); +}; +``` + +The optional use of the `` component makes it possible to highlight relevant parts of the result based on the user's search query. + +**Note on Analytics**: In order for app integrators to track and improve search experiences across Backstage, it's important for them to understand when and what users search for, as well as what they click on after searching. When providing a custom result component, it's your responsibility as a plugin developer to instrument it according to search analytics conventions. In particular: + +- You must use the `analytics.captureEvent` method, from the `useAnalytics()` hook (detailed [plugin analytics docs are here](./analytics.md)). +- You must ensure that the action of the event, representing a click on a search result item, is `discover`, and the subject is the `title` of the clicked result. In addition, the `to` attribute should be set to the result's `location`, and the `value` of the event must be set to the `rank` (passed in as a prop). +- You must ensure that the aforementioned `captureEvent` method is called when a user clicks the link; you should further ensure that the `noTrack` prop is added to the link (which disables default link click tracking, in favor of this custom instrumentation). + +For other examples and inspiration on custom result list items, check out the [``](https://github.com/backstage/backstage/blob/c981e83/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx) or [``](https://github.com/backstage/backstage/blob/c981e83/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx) components. diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index 28c8b410e3..62b3942537 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -4,6 +4,10 @@ title: Integrating Search into a plugin description: How to integrate Search into a Backstage plugin --- +:::info +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + The Backstage Search Platform was designed to give plugin developers the APIs and interfaces needed to offer search experiences within their plugins, while abstracting away (and instead empowering application integrators to choose) the @@ -24,34 +28,25 @@ The search platform provides an interface (`DocumentCollatorFactory` from packag > You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. -#### 1. Install collator interface dependencies +#### 1. Create a collator module package -We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: +In order to add an FAQ collator to the Backstage index registry we have to create a [plugin module](https://backstage.io/docs/backend-system/building-plugins-and-modules/index#modules), and the best way to do this is to create it in a separate package, e.g., `plugins/search-backend-module-faq-snippets-collator`, using the `yarn new` command: + +1. Access your Backstage project root directory and run `yarn new`; +2. When asked about what do you want to create, please select `backend-module` and hit enter; +3. Input `search` as the plugin ID and `faq-snippets-collator` as the module ID; +4. A `search-backend-module-faq-snippets-collator` folder should have been created in your project's "plugins" directory. + +#### 2. Install the collator dependencies + +We will use some libraries in the module, so let's add them to your plugin module dependencies: ```sh -# navigate to the plugin directory -# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) -cd plugins/faq-snippets - # Create a new branch using Git command-line git checkout -b tutorials/new-faq-snippets-collator # Install the package containing the interface -yarn add @backstage/plugin-search-common -``` - -#### 2. Define your document type - -Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. - -Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: - -```ts -import { IndexableDocument } from '@backstage/plugin-search-common'; - -export interface FaqSnippetDocument extends IndexableDocument { - answered_by: string; -} +yarn workspace @internal/backstage-plugin-search-backend-module-faq-snippets-collator add node-fetch @backstage/plugin-search-common @backstage/plugin-search-backend-node ``` #### 3. Use Backstage App configuration @@ -63,7 +58,35 @@ faq: baseUrl: https://backstage.example.biz/faq-snippets ``` -#### 4. Implement your collator +It is optional to define a schedule for the collator to run, or else it defaults to the value in the collator factory code (See [5. Implement the collator factory](#5-implement-the-collator-factory)): + +```yaml +faq: + baseUrl: https://backstage.example.biz/faq-snippets + /* highlight-add-start */ + schedule: + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } + /* highlight-add-end */ +``` + +#### 4. Define the collator document type + +Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. + +Create a new file `plugins/search-backend-module-faq-snippets-collator/src/types.ts` and paste the following below: + +```ts +import { IndexableDocument } from '@backstage/plugin-search-common'; + +export interface FaqSnippetDocument extends IndexableDocument { + answered_by: string; +} +``` + +#### 5. Implement the collator factory Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: @@ -80,52 +103,50 @@ Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq } ``` -Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: +Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/search-backend-module-faq-snippets-collator/src/factory.ts` file: ```ts -import fetch from 'cross-fetch'; -import { Logger } from 'winston'; -import { Config } from '@backstage/config'; +import fetch from 'node-fetch'; import { Readable } from 'stream'; + +import { + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; -import { FaqDocument } from './FaqDocument'; +import { FaqSnippetDocument } from './types'; -export type FaqCollatorFactoryOptions = { - baseUrl?: string; - logger: Logger; -}; +const DEFAULT_BASE_URL = 'https://backstage.example.biz/faq-snippets'; -export class FaqCollatorFactory implements DocumentCollatorFactory { - private readonly baseUrl: string | undefined; - private readonly logger: Logger; +export class FaqSnippetsCollatorFactory implements DocumentCollatorFactory { public readonly type: string = 'faq-snippets'; + private readonly baseUrl: string; + private readonly logger: LoggerService; - private constructor(options: FaqCollatorFactoryOptions) { + private constructor(options: { logger: LoggerService; baseUrl: string }) { this.baseUrl = options.baseUrl; this.logger = options.logger; } - static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { - const baseUrl = - config.getOptionalString('faq.baseUrl') || - 'https://backstage.example.biz/faq-snippets'; - return new FaqCollatorFactory({ ...options, baseUrl }); + static fromConfig( + config: RootConfigService, + options: { + logger: LoggerService; + }, + ) { + const baseUrl = config.getOptionalString('faq.baseUrl') ?? DEFAULT_BASE_URL; + return new FaqSnippetsCollatorFactory({ ...options, baseUrl }); } async getCollator() { return Readable.from(this.execute()); } - async *execute(): AsyncGenerator { - if (!this.baseUrl) { - this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); - return; - } - + async *execute(): AsyncGenerator { + this.logger.info(`Fetching faq snippets from ${this.baseUrl}`); const response = await fetch(this.baseUrl); const data = await response.json(); - for (const faq of data.items) { yield { title: faq.question, @@ -138,13 +159,109 @@ export class FaqCollatorFactory implements DocumentCollatorFactory { } ``` -#### 5. Test your collator +#### 6. Implement the collator plugin module + +Now we have to connect the search backend plugin with our FAQ Snippets collator factory, so replace the `module.ts` file with the content below: + +```ts title='plugins/search-backend-module-faq-snippets-collator/src/module.ts' +import { + coreServices, + createBackendModule, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; +import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha'; +import { FaqSnippetsCollatorFactory } from './factory'; + +export const searchFaqSnippetsCollatorModule = createBackendModule({ + pluginId: 'search', + moduleId: 'faq-snippets-collator', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + indexRegistry: searchIndexRegistryExtensionPoint, + }, + async init({ config, logger, scheduler, indexRegistry }) { + const defaultSchedule = { + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, + }; + + const schedule = config.has('faq.schedule') + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('faq.schedule'), + ) + : defaultSchedule; + + indexRegistry.addCollator({ + schedule: scheduler.createScheduledTaskRunner(schedule), + factory: FaqSnippetsCollatorFactory.fromConfig(config, { logger }), + }); + }, + }); + }, +}); +``` + +In the fragment above, the module is registered, and when the Backstage backend initializes it, it adds the FAQ Snippets collator to the search index registry. Now let's export the module as default from the `index.ts` file: + +```ts title='plugins/search-backend-module-faq-snippets-collator/src/index.ts' +export { searchFaqSnippetsCollatorModule as default } from './module'; +``` + +#### 7. Install the collator module + +The newly created module should be added to the backend package dependencies as follows: + +```sh +yarn --cwd packages/backend add @internal/backstage-plugin-search-backend-module-faq-snippets-collator +``` + +After that, install the module on your Backstage backend instance: + +```ts title='packages/backend/src/index.ts' +import { createBackend } from '@backstage/backend-defaults'; +//... +const backend = createBackend(); +// Installing the search backend plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); +// Installing the newly created faq snippets collator module +backend.add( + import( + '@internal/backstage-plugin-search-backend-module-faq-snippets-collator' + ), +); +//... +backend.start(); +``` + +#### 8. Testing the collator code To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/docs/reference/plugin-search-backend-node.testpipeline) utility that emulates a pipeline into which you can integrate your custom collator. Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. -#### 6. Make your plugins collator discoverable for others +You can also check out the documentation on [how to test Backstage plugin modules](../backend-system/building-plugins-and-modules/02-testing.md). + +#### 9. Running the collator locally + +Run `yarn dev` in the root folder of your Backstage project and look for logs like these: + +```sh +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Task worker starting: search_index_faq_snippets, {"version":2,"cadence":"PT10M","initialDelayDuration":"PT3S","timeoutAfterDuration":"PT15M"} task=search_index_faq_snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Collating documents for faq-snippets via FaqSnippetsCollatorFactory documentType=faq-snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Fetching faq snippets from https://backstage.example.biz/faq-snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Collating documents for faq-snippets succeeded documentType=faq-snippets +``` + +It means that the collator task was started and completed successfully. Visit http://localhost:3000, log in, select the 'All' tab, and type in one of your snippets title in the search box. + +Results should appear for snippets. + +#### 10. Make your plugins collator discoverable for others If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 0f48e35a25..ba3f694d0c 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -151,32 +151,52 @@ export const myPluginTranslationRef = createTranslationRef({ Step 1: Create translation resources +You should separate different translations to their own files and import them in the main file: + ```ts // packages/app/src/translations/userSettings.ts import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha'; -export const userSettingsMessages = createTranslationResource({ +export const userSettingsTranslations = createTranslationResource({ ref: userSettingsTranslationRef, translations: { - zh: () => - Promise.resolve({ - default: { - 'languageToggle.title': '语言', - 'languageToggle.select': '选择{{language}}', - 'languageToggle.description': '切换语言', - 'themeToggle.title': '主题', - 'themeToggle.description': '切换主题', - 'themeToggle.select': '选择{{theme}}', - 'themeToggle.selectAuto': '选择自动主题', - 'themeToggle.names.auto': '自动', - 'themeToggle.names.dark': '暗黑', - 'themeToggle.names.light': '明亮', - }, - }), + zh: () => import('./userSettings-zh'), }, }); + +// packages/app/src/translations/userSettings-zh.ts +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha'; + +const zh = createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, // False means that this is a partial translation + messages: { + 'languageToggle.title': '语言', + 'languageToggle.select': '选择{{language}}', + }, +}); + +export default zh; +``` + +It's also possible to export the list of messages directly: + +```ts +// packages/app/src/translations/userSettings-zh.ts +export default { + 'languageToggle.title': '语言', + 'languageToggle.select': '选择{{language}}', + 'languageToggle.description': '切换语言', + 'themeToggle.title': '主题', + 'themeToggle.description': '切换主题', + 'themeToggle.select': '选择{{theme}}', + 'themeToggle.selectAuto': '选择自动主题', + 'themeToggle.names.auto': '自动', + 'themeToggle.names.dark': '暗黑', + 'themeToggle.names.light': '明亮', +}; ``` You should change `zh` under the translations object to your local language. @@ -186,12 +206,12 @@ Step 2: Config translations in `packages/app/src/App.tsx` In an app you can both override the default messages, as well as register translations for additional languages: ```diff -+ import { userSettingsMessages } from './translations/userSettings'; ++ import { userSettingsTranslations } from './translations/userSettings'; const app = createApp({ + __experimentalTranslations: { + availableLanguages: ['en', 'zh'], -+ resources: [userSettingsMessages], ++ resources: [userSettingsTranslations], + }, }) ``` diff --git a/docs/plugins/url-reader.md b/docs/plugins/url-reader.md deleted file mode 100644 index 9fab0482bb..0000000000 --- a/docs/plugins/url-reader.md +++ /dev/null @@ -1,265 +0,0 @@ ---- -id: url-reader -title: URL Reader -sidebar_label: URL Reader -# prettier-ignore -description: URL Reader is a backend core API responsible for reading files from external locations. ---- - -## Concept - -Some of the core plugins of Backstage have to read files from an external -location. [Software Catalog](../features/software-catalog/index.md) has to read -the [`catalog-info.yaml`](../features/software-catalog/descriptor-format.md) -entity descriptor files to register and track an entity. -[Software Templates](../features/software-templates/index.md) have to download -the template skeleton files before creating a new component. -[TechDocs](../features/techdocs/README.md) has to download the markdown source -files before generating a documentation site. - -Since, the requirement for reading files is so essential for Backstage plugins, -the -[`coreServices.urlReader`](../reference/backend-plugin-api.coreservices.urlreader.md) -package provides a dedicated API for reading from such URL based remote -locations like GitHub, GitLab, Bitbucket, Google Cloud Storage, etc. This is -commonly referred to as "URL Reader". It takes care of making authenticated -requests to the remote host so that private files can be read securely. If users -have [GitHub App based authentication](../integrations/github/github-apps.md) set up, URL Reader even -refreshes the token, to avoid reaching the GitHub API rate limit. - -As a result, plugin authors do not have to worry about any of these problems -when trying to read files. - -## Interface - -When the Backstage backend starts, a new instance of URL Reader is created. You -can see this in the index file of your Backstage backend -i.e.`packages/backend/src/index.ts`. -[Example](https://github.com/backstage/backstage/blob/ebbe91dbe79038a61d35cf6ed2d96e0e0d5a15f3/packages/backend/src/index.ts#L57) - -```ts -// File: packages/backend/src/index.ts - -import { UrlReaders } from '@backstage/backend-common'; - -function makeCreateEnv(config: Config) { - // .... - const reader = UrlReaders.default({ logger: root, config }); - // -} -``` - -This instance contains all the default URL Reader providers -in the backend-defaults package including GitHub, GitLab, Bitbucket, Azure, Google -GCS. As the need arises, more URL Readers are being written to support different -providers. - -The generic interface of a URL Reader instance looks like this. - -```ts -export type UrlReader = { - /* Used to read a single file and return its content. */ - readUrl(url: string, options?: ReadUrlOptions): Promise; - /* Used to read a file tree and download as a directory. */ - readTree(url: string, options?: ReadTreeOptions): Promise; - /* Used to search a file in a tree. */ - search(url: string, options?: SearchOptions): Promise; -}; -``` - -## Using a URL Reader inside a plugin - -The `reader` instance is available in the backend plugin environment and passed -on to all the backend plugins. You can see an -[example](https://github.com/backstage/backstage/blob/b0be185369ebaad22255b7cdf18535d1d4ffd0e7/packages/backend/src/plugins/techdocs.ts#L31). -When any of the methods on this instance is called with a URL, URL Reader -extracts the host for that URL (e.g. `github.com`, `ghe.mycompany.com`, etc.). -Using the -[`@backstage/integration`](https://github.com/backstage/backstage/tree/master/packages/integration) -package, it looks inside the -[`integrations:`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/app-config.yaml#L134-L158) -config of the `app-config.yaml` to find out how to work with the host based on -the configs provided like authentication token, API base URL, etc. - -Make sure your plugin-specific backend file at -`packages/backend/src/plugins/.ts` is forwarding the `reader` instance -passed on as the `PluginEnvironment` to the actual plugin's `createRouter` -function. See how this is done in -[Catalog](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend/src/plugins/catalog.ts#L25-L27) -and -[TechDocs](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend/src/plugins/techdocs.ts#L31-L36) -backend plugins. - -Once the reader instance is available inside the plugin, one of its methods can -directly be used with a URL. Some example usages - - -- [`readUrl`](https://github.com/backstage/backstage/blob/a7607b5/plugins/catalog-backend/src/modules/codeowners/lib/read.ts#L24-L33) - - Catalog using the `readUrl` method to read the CODEOWNERS file in a repository. -- [`readTree`](https://github.com/backstage/backstage/blob/84a8788/plugins/techdocs-node/src/helpers.ts#L146-L167) - - TechDocs using the `readTree` method to download markdown files in order to - generate the documentation site. -- [`readTree`](https://github.com/backstage/backstage/blob/cb4f0e4/plugins/techdocs-node/src/stages/prepare/url.ts#L51-L73) - - TechDocs using `NotModifiedError` to maintain cache and speed up and limit the - number of requests. -- [`search`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts#L88-L108) - - Catalog using the `search` method to find files for a location URL containing - a glob pattern. - -Note that URL Readers which target git-based version control systems may, under -the hood, leverage the ability to create tar archives based on a specific git -commit-ish. A consequence of this is that files and directories configured with -[the `export-ignore` attribute](https://git-scm.com/docs/gitattributes#_creating_an_archive) -via `.gitattributes` will not be visible to the URL reader when using the -`readTree` or `search` methods. - -Be aware of this limitation and ensure that end-users of your plugin are also -aware via, for example, documentation. - -## Writing a new URL Reader - -If the available URL Readers are not sufficient for your use case and you want -to add a new URL Reader for any other provider, you are most welcome to -contribute one! - -Feel free to use the -[GitHub URL Reader](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/GithubUrlReader.ts) -as a source of inspiration. - -### 1. Add an integration - -The provider for your new URL Reader can also be called an "integration" in -Backstage. The `integrations:` section of your Backstage `app-config.yaml` -config file is supposed to be the place where a Backstage integrator defines the -host URL for the integration, authentication details and other integration -related configurations. - -The `@backstage/integration` package is where most of the integration specific -code lives, so that it is shareable across Backstage. Functions like "read the -integrations config and process it", "construct headers for authenticated -requests to the host" or "convert a plain file URL into its API URL for -downloading the file" would live in this package. - -### 2. Create the URL Reader - -Create a new class which implements the -[`UrlReader` type](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/types.ts#L21-L28) -inside `@backstage/backend-common`. Create and export a static `factory` method -which reads the integration config and returns a map of host URLs the new reader -should be used for. See the -[GitHub URL Reader](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/GithubUrlReader.ts#L50-L63) -for example. - -### 3. Implement the methods - -We want to make sure all URL Readers behave in the same way. Hence if possible, -all the methods of the `UrlReader` interface should be implemented. However it -is okay to start by implementing just one of them and create issues for the -remaining. - -#### `readUrl` - -`readUrl` method expects a user-friendly URL, something which can be copied from -the browser naturally when a person is browsing the provider in their browser. - -- ✅ Valid URL : - `https://github.com/backstage/backstage/blob/master/ADOPTERS.md` -- ❌ Not a valid URL : - `https://raw.githubusercontent.com/backstage/backstage/master/ADOPTERS.md` -- ❌ Not a valid URL : `https://github.com/backstage/backstage/ADOPTERS.md` - -Upon receiving the URL, `readUrl` converts the user-friendly URL into an API URL -which can be used to request the provider's API. - -`readUrl` then makes an authenticated request to the provider API and returns the response containing the file's contents and ETag(if the provider supports it). - -#### `readTree` - -`readTree` method also expects user-friendly URLs similar to `read` but the URL -should point to a tree (could be the root of a repository or even a -sub-directory). - -- ✅ Valid URL : `https://github.com/backstage/backstage` -- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master` -- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/docs` - -Using the provider's API documentation, find out an API endpoint which can be -used to download either a zip or a tarball. You can download the entire tree -(e.g. a repository) and filter out in case the user is expecting only a -sub-tree. But some APIs are smart enough to accept a path and return only a -sub-tree in the downloaded archive. - -#### search - -`search` method expects a glob pattern of a URL and returns a list of files -matching the query. - -- ✅ Valid URL : - `https://github.com/backstage/backstage/blob/master/**/catalog-info.yaml` -- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/**/*.md` -- ✅ Valid URL : - `https://github.com/backstage/backstage/blob/master/*/package.json` -- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/READM` - -The core logic of `readTree` can be used here to extract all the files inside -the tree and return the files matching the pattern in the `url`. - -### 4. Add to available URL Readers - -There are two ways to make your new URL Reader available for use. - -You can choose to make it open source, by updating the -[`default` factory](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/UrlReaders.ts#L62-L81) -method of URL Readers. - -But for something internal which you don't want to make open source, you can -update your `packages/backend/src/index.ts` file and update how the `reader` -instance is created. - -```ts -// File: packages/backend/src/index.ts -const reader = UrlReaders.default({ - logger: root, - config, - // This is where your internal URL Readers would go. - factories: [myCustomReader.factory], -}); -``` - -### 5. Caching - -All of the methods above support an ETag based caching. If the method is called -without an `etag`, the response contains an ETag of the resource (should ideally -forward the ETag returned by the provider). If the method is called with an -`etag`, it first compares the ETag and returns a `NotModifiedError` in case the -resource has not been modified. This approach is very similar to the actual -[`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and -[`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) -HTTP headers. - -### 6. Debugging - -When debugging one of the URL Readers, you can straightforward use the -[`reader` instance created](https://github.com/backstage/backstage/blob/ebbe91dbe79038a61d35cf6ed2d96e0e0d5a15f3/packages/backend/src/index.ts#L57) -when the backend starts and call one of the methods with your debugging URL. - -```ts -// File: packages/backend/src/index.ts - -async function main() { - // ... - const createEnv = makeCreateEnv(config); - - const testReader = createEnv('test-url-reader').reader; - const response = await testReader.readUrl( - 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', - ); - console.log((await response.buffer()).toString()); - // ... -} -``` - -This will be run every time you restart the backend. Note that after any change -in the URL Reader code, you need to stop the backend and restart, since the -`reader` instance is memoized and does not update on hot module reloading. Also, -there are a lot of unit tests written for the URL Readers, which you can make -use of. 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.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.31.0-next.0-changelog.md b/docs/releases/v1.31.0-next.0-changelog.md new file mode 100644 index 0000000000..7e8b118fff --- /dev/null +++ b/docs/releases/v1.31.0-next.0-changelog.md @@ -0,0 +1,2787 @@ +# Release v1.31.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.31.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.31.0-next.0) + +## @backstage/backend-app-api@0.10.0-next.0 + +### Minor Changes + +- 19ff127: **BREAKING**: The deprecated `identityServiceFactory` and `tokenManagerServiceFactory` have been removed. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- cd38da8: Deprecate the `featureDiscoveryServiceFactory` in favor of using `@backstage/backend-defaults#discoveryFeatureLoader` instead. +- 7f779c7: `auth.externalAccess` should be optional in the config schema +- 51a69b5: Fix feature loaders in CJS double-default nested builds +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.25.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 8ba77ed: The `legacyPlugin` and `makeLegacyPlugin` helpers now provide their own shim implementation of the identity and token manager services, as these services are being removed from the new backend system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 2e9ec14: Add `pg-format` as a dependency +- 19ff127: Internal refactor to re-declare the token manager service which was removed from `@backstage/backend-plugin-api`, but is still supported in this package for backwards compatibility. +- 66dbf0a: Allow the cache service to accept the human duration format for TTL +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.5.0-next.0 + +### Minor Changes + +- 359fcd7: **BREAKING**: The backwards compatibility with plugins using legacy auth through the token manager service has been removed. This means that instead of falling back to using the old token manager, requests towards plugins that don't support the new auth system will simply fail. Please make sure that all plugins in your deployment are hosted within a backend instance from the new backend system. + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +- 19ff127: **BREAKING**: The default backend instance no longer provides implementations for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. + + If you rely on plugins that still require these services, you can add them to your own backend by re-creating the service reference and factory. + + The following can be used to implement the identity service: + + ```ts + import { + coreServices, + createServiceFactory, + createServiceRef, + } from '@backstage/backend-plugin-api'; + import { + DefaultIdentityClient, + IdentityApi, + } from '@backstage/plugin-auth-node'; + + backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.identity' }), + deps: { + discovery: coreServices.discovery, + }, + async factory({ discovery }) { + return DefaultIdentityClient.create({ discovery }); + }, + }), + ); + ``` + + The following can be used to implement the token manager service: + + ```ts + import { ServerTokenManager, TokenManager } from '@backstage/backend-common'; + import { createBackend } from '@backstage/backend-defaults'; + import { + coreServices, + createServiceFactory, + createServiceRef, + } from '@backstage/backend-plugin-api'; + + backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.tokenManager' }), + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + createRootContext({ config, logger }) { + return ServerTokenManager.fromConfig(config, { + logger, + allowDisabledTokenManager: true, + }); + }, + async factory(_deps, tokenManager) { + return tokenManager; + }, + }), + ); + ``` + +### Patch Changes + +- 7f779c7: `auth.externalAccess` should be optional in the config schema + +- 7a72ec8: Exports the `discoveryFeatureLoader` as a replacement for the deprecated `featureDiscoveryService`. + The `discoveryFeatureLoader` is a new backend system [feature loader](https://backstage.io/docs/backend-system/architecture/feature-loaders/) that discovers backend features from the current `package.json` and its dependencies. + Here is an example using the `discoveryFeatureLoader` loader in a new backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { discoveryFeatureLoader } from '@backstage/backend-defaults'; + //... + + const backend = createBackend(); + //... + backend.add(discoveryFeatureLoader); + //... + backend.start(); + ``` + +- 66dbf0a: Allow the cache service to accept the human duration format for TTL + +- 5a8fcb4: Added the option to skip database migrations by setting `skipMigrations: true` in config. This can be done globally in the database config or by plugin id. + +- 0b2a402: Updates to the config schema to match reality + +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-dynamic-feature-service@0.4.0-next.0 + +### Minor Changes + +- 9080f57: **BREAKING**: `dynamicPluginsServiceFactory` is no longer callable as a function. If you need to provide options to make a custom factory, use `dynamicPluginsSchemasServiceFactoryWithOptions` instead. + +### Patch Changes + +- cd38da8: Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using the `dynamicPluginsFeatureDiscoveryLoader` to discover dynamic features in a new backend system. + + See usage examples below: + + Example using the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + //... + + const backend = createBackend(); + backend.add(dynamicPluginsFeatureDiscoveryLoader); + //... + backend.start(); + ``` + + Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + import { myCustomModuleLoader } from './myCustomModuleLoader'; + //... + + const backend = createBackend(); + backend.add( + dynamicPluginsFeatureDiscoveryLoader({ + moduleLoader: myCustomModuleLoader, + }), + ); + //... + backend.start(); + ``` + +- e27f889: Relax type check for a plugin's default export to also accept a BackendFeature defined as a function instead of an object + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. + +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-app-node@0.1.25-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-backend@0.3.12-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/plugin-search-backend-node@1.3.2-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 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/backend-plugin-api@0.9.0-next.0 + +### Minor Changes + +- 19ff127: **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. + +- f687050: Removed the following deprecated exports + + - `BackendPluginConfig` use `CreateBackendPluginOptions` + - `BackendModuleConfig` use `CreateBackendModuleOptions` + - `ExtensionPointConfig` use `CreateExtensionPointOptions` + +- 4d82481: Removed deprecated `ServiceFactoryOrFunction` type. + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- cd38da8: Deprecate the `featureDiscoveryServiceRef` in favor of using the new `discoveryFeatureLoader` instead. +- 66dbf0a: Allow the cache service to accept the human duration format for TTL +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-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-permission-common@0.8.1 + +## @backstage/backend-test-utils@0.6.0-next.0 + +### Minor Changes + +- 19ff127: **BREAKING**: Removed service mocks for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 0363bf1: There is a new `mockErrorHandler` utility to help in mocking the error middleware in tests. +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/frontend-app-api@0.9.0-next.0 + +### Minor Changes + +- 62cce6c: Removed deprecated `icons` property passing to `createApp` and `createSpecializedApp`. Use `IconBundleBlueprint.make` to create extensions instead and include them in the app. + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. + +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. + +- f3a2b91: Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension. + +- 5446061: Internal refactor following removal of v1 extension support. The app implementation itself still supports v1 extensions at runtime. + +- 98850de: Added support for defining `replaces` in `createExtensionInput` which will allow extensions to redirect missing `attachTo` points to an input of the created extension. + + ```ts + export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + // attachTo: { id: 'app', input: 'themes'} will be redirected to this input instead + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: () { + ... + } + }); + ``` + +- 4a66456: Added the `root` extension the replace the `app` extension as the root of the app. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @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/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.8.0-next.0 + +### Minor Changes + +- 5446061: **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. +- fec8b57: **BREAKING**: Updated the type parameters for `ExtensionDefinition` and `ExtensionBlueprint` to only have a single object parameter. The base type parameter is exported as `ExtensionDefinitionParameters` and `ExtensionBlueprintParameters` respectively. This is shipped as an immediate breaking change as we expect usage of these types to be rare, and it does not affect the runtime behavior of the API. + + This is a breaking change as it changes the type parameters. Existing usage can generally be updated as follows: + + - `ExtensionDefinition` -> `ExtensionDefinition` + - `ExtensionDefinition` -> `ExtensionDefinition` + - `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig }>` + - `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig, configInput: TConfigInput }>` + + If you need to infer the parameter you can use `ExtensionDefinitionParameters`, for example: + + ```ts + import { + ExtensionDefinition, + ExtensionDefinitionParameters, + } from '@backstage/frontend-plugin-api'; + + function myUtility( + ext: ExtensionDefinition, + ): T['config'] { + // ... + } + ``` + + The same patterns apply to `ExtensionBlueprint`. + + This change is made to improve the readability of API references and ability to evolve the type parameters in the future. + +### Patch Changes + +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. + +- f3a2b91: Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension. + +- 98850de: Added support for defining `replaces` in `createExtensionInput` which will allow extensions to redirect missing `attachTo` points to an input of the created extension. + + ```ts + export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + // attachTo: { id: 'app', input: 'themes'} will be redirected to this input instead + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: () { + ... + } + }); + ``` + +- 4a66456: A new `apis` parameter has been added to `factory` for extensions. This is a way to access utility APIs without being coupled to the React context. + +- 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/frontend-test-utils@0.2.0-next.0 + +### Minor Changes + +- 5446061: Removed support for testing "v1" extensions, where outputs are defined as an object rather than an array. +- e6e488c: **BREAKING**: The deprecated `.render()` method has been removed from the extension tester. + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 4a66456: Internal update to add support for passing an `ApiRegistry` when creating the node tree +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. +- f6d1874: Added the ability to provide additional `extensions` and `features` to `renderInTestApp` +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/test-utils@1.6.0-next.0 + +### Minor Changes + +- d47be30: Added the icons option to the renderInTestApp function's TestAppOptions to be forwarded to the icons option of `createApp`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app@0.1.0-next.0 + +### Minor Changes + +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-auth-backend@0.23.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- ecbc47e: Fix a bug where the signer was checked from the payload instead of the header +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.9.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @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-logs@0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @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.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-devtools-backend@0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-events-node@0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + +## @backstage/plugin-notifications-backend@0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.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.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.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-permission-backend-module-allow-all-policy@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-scaffolder@1.25.0-next.0 + +### Minor Changes + +- 5143616: Added EntityOwnerPicker component to the TemplateListPage to allow filtering on owner + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @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.25.0-next.0 + +### Minor Changes + +- 62898bd: `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. + +### Patch Changes + +- c160951: Found the issue during testing the clean up of the workspace for the database implementation. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 9e5923d: Added test cases for publish:gerrit action examples +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.0 + +## @backstage/plugin-scaffolder-react@1.12.0-next.0 + +### Minor Changes + +- 4512f71: Add `ui:backstage.review.name` option for custom item names on scaffolder review page, and also add support for rendering the `title` property instead of the key name. + +### Patch Changes + +- 3ebb64f: - Fix secret widget field not displaying as required. + - Fix secret widget not able to be required inside nested objects. + - Fix secret widget not able to be disabled. + - Support `minLength` and `maxLength` properties for secret widget. +- 8dd6ef6: Fix an issue where keys with duplicate final key parts are not all displayed in the `ReviewState`. Change the way the keys are formatted to include the full schema path, separated by `>`. +- 9a0672a: Scaffolder review page shows static amount of asterisks for secret fields. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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-stack-overflow-collator@0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-react@1.8.0-next.0 + +### Minor Changes + +- 9d66d8c: Make use of the `useApp` hook to retrieve the specified search icon in the SearchBar + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- f26ff99: Slight type tweak to match newer React versions better +- 5446061: The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + - @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 + +## @backstage/plugin-signals-backend@0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 3ec5ccb: The `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/backend-openapi-utils@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/errors@1.2.4 + +## @backstage/backend-tasks@0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/cli@0.27.1-next.0 + +### Patch Changes + +- 1b5c264: Add `checks: 'read'` for default GitHub app permissions +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.2.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.19-next.0 + +### Patch Changes + +- 019d9ad: Minor dockerfile syntax update +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## @backstage/repo-tools@0.9.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.11-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-api-docs@0.11.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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.74-next.0 + +### Patch Changes + +- d3f79d1: Fixing dependency metadata with the new `@backstage/plugin-app` package +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-app-node@0.1.25-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-app-node@0.1.25-next.0 + +### Patch Changes + +- d3f79d1: Fixing dependency metadata with the new `@backstage/plugin-app` package +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/config-loader@1.9.0 + +## @backstage/plugin-app-visualizer@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-node@0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog@1.22.1-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 5446061: The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). +- 180a45f: Entity presentation api now only fetches fields that are required to display entity title +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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-search-common@1.2.14 + +## @backstage/plugin-catalog-backend@1.25.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 53cce86: Fixed an issue with the by-query call, where ordering by a field that does not exist on all entities led to not all results being returned +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @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.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.2-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## @backstage/plugin-catalog-backend-module-github@0.7.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- b446954: Remove dependency on backend-common +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 4b28e39: Updated the README to include documentation for the new backend support +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @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.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-graph@0.4.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.3-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-react@1.12.4-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 5446061: The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-devtools@0.1.18-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 019d9ad: Minor dockerfile syntax update +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/core-components@0.14.10 + - @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-events-backend@0.3.12-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-azure@0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## @backstage/plugin-events-backend-module-github@0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-test-utils@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.0 + +## @backstage/plugin-home@0.7.10-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + +## @backstage/plugin-kubernetes@0.11.14-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- bfc0f42: Make k8s entity content appear on components & resources only by default in new FE system +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.2 + +## @backstage/plugin-kubernetes-backend@0.18.6-next.0 + +### Patch Changes + +- f55f8bf: The `KubernetesBuilder` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-kubernetes-node@0.1.19-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.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-common@0.8.2 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.2 + +## @backstage/plugin-kubernetes-node@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-notifications@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-react@0.0.5-next.0 + - @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-notifications-node@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-org@0.6.29-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-org-react@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.49-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- fcb9356: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-permission-node@0.8.3-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-proxy-backend@0.5.6-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- d298e6e: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.0 + - @backstage/backend-test-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-search@1.4.16-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/core-components@0.14.10 + - @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 + +## @backstage/plugin-search-backend@1.5.17-next.0 + +### Patch Changes + +- 5726390: Deprecate create router as the legacy backend system will no longer be supported. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. + +- 5726390: The following collator factories are deprecated, please [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) to the new backend system and follow the instructions below to install collators via module: + + - `DefaultCatalogCollatorFactory`: ; + - `ToolDocumentCollatorFactory`: ; + - `DefaultTechDocsCollatorFactory`: . + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 5726390: Internal refactor to use `LoggerService` and `DatabaseService` instead of the legacy `Logger` and `PluginDatabaseManager` types. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-explore@0.2.2-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. + +- 5726390: The following collator factories are deprecated, please [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) to the new backend system and follow the instructions below to install collators via module: + + - `DefaultCatalogCollatorFactory`: ; + - `ToolDocumentCollatorFactory`: ; + - `DefaultTechDocsCollatorFactory`: . + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-pg@0.5.35-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 5726390: Internal refactor to use `LoggerService` and `DatabaseService` instead of the legacy `Logger` and `PluginDatabaseManager` types. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. + +- 5726390: The following collator factories are deprecated, please [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) to the new backend system and follow the instructions below to install collators via module: + + - `DefaultCatalogCollatorFactory`: ; + - `ToolDocumentCollatorFactory`: ; + - `DefaultTechDocsCollatorFactory`: . + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-techdocs-node@1.12.11-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-node@1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-signals@0.0.10-next.0 + +### Patch Changes + +- 5add8e1: Added a `SignalsDisplay` extension to allows the signals plugin to be installed in an app as follows: + + ```tsx + export default app.createRoot( + <> + + + + + + {routes} + + , + ); + ``` + + With this in place you can remove the explicit installation via the `plugins` option for `createApp`. + +- Updated dependencies + - @backstage/plugin-signals-react@0.0.5-next.0 + - @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-node@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-react@0.0.5-next.0 + +### Patch Changes + +- 0389801: Fix for `useSignal` returning the inverse value for `isSignalsAvailable`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-techdocs@1.10.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + +## @backstage/plugin-techdocs-backend@1.10.13-next.0 + +### Patch Changes + +- 086c32d: Dedicated token for techdocs cache sync +- 5b679ac: The `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-techdocs-node@1.12.11-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + +## @backstage/plugin-techdocs-node@1.12.11-next.0 + +### Patch Changes + +- 4417dd4: Fix typo and unify TechDocs casing in doc strings +- 33ebb28: As the `@backstage/backend-common` package is deprecated, we have updated the `techdocs-node` package to stop depending on it. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.8-next.0 + +### Patch Changes + +- 5ee3d27: Fixed issue in useShadowRootElements which could lead to unlimited render loops +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.12-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-signals-react@0.0.5-next.0 + - @backstage/core-app-api@1.14.2 + - @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-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.24-next.0 + +### Patch Changes + +- 164ce3e: In preparation to stop supporting to the legacy backend system, the `createRouter` function is now deprecated and we strongly recommend you [migrate](https://backstage.io/docs/backend-system/building-backends/migrating) your backend to the new system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 1b98099: Replaced usage of the deprecated identity service with the new HTTP auth service for the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @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.101-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/plugin-catalog-import@0.12.3-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-user-settings@0.8.12-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-kubernetes@0.11.14-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.0 + - @backstage/plugin-api-docs@0.11.9-next.0 + - @backstage/plugin-devtools@0.1.18-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/plugin-search@1.4.16-next.0 + - @backstage/plugin-home@0.7.10-next.0 + - @backstage/plugin-org@0.6.29-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/plugin-signals@0.0.10-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-notifications@0.3.1-next.0 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-search-common@1.2.14 + +## example-app-next@0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-import@0.12.3-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-user-settings@0.8.12-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-kubernetes@0.11.14-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.0 + - @backstage/plugin-api-docs@0.11.9-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/plugin-search@1.4.16-next.0 + - @backstage/plugin-home@0.7.10-next.0 + - @backstage/plugin-org@0.6.29-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/plugin-signals@0.0.10-next.0 + - @backstage/plugin-app-visualizer@0.1.10-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-notifications@0.3.1-next.0 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-search-common@1.2.14 + +## app-next-example-plugin@0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + +## example-backend@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.13-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend@1.5.17-next.0 + - @backstage/plugin-kubernetes-backend@0.18.6-next.0 + - @backstage/plugin-scaffolder-backend@1.25.0-next.0 + - @backstage/plugin-app-backend@0.3.74-next.0 + - @backstage/plugin-signals-backend@0.2.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-permission-backend@0.5.49-next.0 + - @backstage/plugin-proxy-backend@0.5.6-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.0 + - @backstage/plugin-devtools-backend@0.4.0-next.0 + - @backstage/plugin-notifications-backend@0.4.0-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-permission-common@0.8.1 + +## example-backend-legacy@0.2.102-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.13-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend@1.5.17-next.0 + - @backstage/plugin-kubernetes-backend@0.18.6-next.0 + - @backstage/plugin-scaffolder-backend@1.25.0-next.0 + - @backstage/plugin-app-backend@0.3.74-next.0 + - @backstage/plugin-signals-backend@0.2.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-backend@0.3.12-next.0 + - @backstage/plugin-permission-backend@0.5.49-next.0 + - @backstage/plugin-proxy-backend@0.5.6-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.0 + - @backstage/plugin-devtools-backend@0.4.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-permission-common@0.8.1 + +## e2e-test@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.100-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## @internal/plugin-todo-list-backend@1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 diff --git a/docs/releases/v1.31.0-next.1-changelog.md b/docs/releases/v1.31.0-next.1-changelog.md new file mode 100644 index 0000000000..eb9b11125e --- /dev/null +++ b/docs/releases/v1.31.0-next.1-changelog.md @@ -0,0 +1,2176 @@ +# Release v1.31.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.31.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.31.0-next.1) + +## @backstage/backend-common@0.25.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 988c145: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + - The deprecated `dropDatabase` function has now been removed, without replacement. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.5.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 055b75b: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + For the `/database` subpath exports: + + - The deprecated `dropDatabase` function has now been removed, without replacement. + - The deprecated `LegacyRootDatabaseService` type has now been removed. + - The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. + - `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + + For the `/cache` subpath exports: + + - The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. + - Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. + +### Patch Changes + +- 622360e: Move down the discovery config to be in the root +- fe6fd8c: Accept `ConfigService` instead of `Config` in constructors/factories +- 5705424: Wrap scheduled tasks from the scheduler core service now in OpenTelemetry spans +- b2a329d: Properly indent the config schema +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/core-compat-api@0.3.0-next.1 + +### Minor Changes + +- 6db849e: **BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-app-api@0.9.0-next.1 + +### Minor Changes + +- 7c80650: **BREAKING**: The `createSpecializedApp` function now creates a bare-bones app without any of the default app structure or APIs. To re-introduce this functionality if you need to use `createSpecializedApp` you can install the `app` plugin from `@backstage/plugin-app`. + + In addition, the `createApp` and `CreateAppFeatureLoader` exports are now deprecated as they are being moved to `@backstage/frontend-defaults`, which should be used instead. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-defaults@0.1.0-next.0 + +### Minor Changes + +- 7c80650: Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`. + +### Patch Changes + +- 7d19cd5: Added a new `CreateAppOptions` type for the `createApp` options. +- 7d19cd5: Added `createPublicSignInApp`, used to creating apps for the public entry point. +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/app-defaults@1.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @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-app-api@0.10.0-next.1 + +### Patch Changes + +- c246372: Updated the error message for missing service dependencies to include the plugin and module IDs. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/backend-dynamic-feature-service@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/backend-openapi-utils@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-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-permission-common@0.8.1 + +## @backstage/backend-test-utils@0.6.0-next.1 + +### Patch Changes + +- 710f621: Added missing service mock for `mockServices.rootConfig.mock`, and fixed the definition of `mockServices.rootHttpRouter.factory` to not have a duplicate callback. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/catalog-client@1.6.7-next.0 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/cli@0.27.1-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common + +- f865103: Updated dependency `esbuild` to `^0.23.0`. + +- 569c3f0: Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/codemods@0.1.50-next.0 + +### Patch Changes + +- 0894166: Updated dependency `jscodeshift` to `^0.16.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/core-components@0.14.11-next.0 + +### Patch Changes + +- 06b8206: Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` +- 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.19-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common + +- bf370c2: Remove references to the `@backstage/backend-tasks` in versions of the `create-app` package, as it has been deprecated. + +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## @backstage/frontend-plugin-api@0.8.0-next.1 + +### Patch Changes + +- c816e2d: Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + + Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead + +- Updated dependencies + - @backstage/core-components@0.14.11-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.2.0-next.1 + +### Patch Changes + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.7-next.1 + +### Patch Changes + +- 5c4aa2f: Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-api-docs@0.11.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app@0.1.0-next.1 + +### Patch Changes + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app-backend@0.3.74-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + +## @backstage/plugin-app-node@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config-loader@1.9.0 + +## @backstage/plugin-app-visualizer@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.1 + +### Patch Changes + +- 8d1fb8d: Throw correct error when email is missing from the claims +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-auth-node@0.5.2-next.1 + +### Patch Changes + +- c46eb0f: Extend the "unable to resolve user identity" message +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-react@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog@1.22.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @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-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + +## @backstage/plugin-catalog-backend@1.25.3-next.1 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-azure@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + +### Patch Changes + +- 53b24d9: Internal update to use the new cache manager +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @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-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-graph@0.4.9-next.1 + +### Patch Changes + +- da91078: Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-catalog-react@1.12.4-next.1 + +### Patch Changes + +- ae9b6cb: Small internal fix to better work with recent `lodash` versions +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @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-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + +### Patch Changes + +- 4f08c85: Show additional info on DevTools unprocessed entities table + + - Location path (so that it's easier to search the failed entity from the YAML URL) + - Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.59-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @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.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-events-backend@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-github@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-node@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-home@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.17-next.0 + +## @backstage/plugin-home-react@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## @backstage/plugin-kubernetes-backend@0.18.6-next.1 + +### Patch Changes + +- ca96b66: Skip start without proper config +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.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-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-node@0.1.19-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## @backstage/plugin-kubernetes-node@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-react@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @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@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-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.5-next.0 + +## @backstage/plugin-notifications-backend@0.4.0-next.1 + +### Patch Changes + +- f195972: Validate notification link when new notification is created +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.0-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + +## @backstage/plugin-notifications-node@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-org@0.6.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-permission-node@0.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-proxy-backend@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.25.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @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.25.0-next.1 + +### Patch Changes + +- f865103: Updated dependency `esbuild` to `^0.23.0`. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.1 + +## @backstage/plugin-scaffolder-node@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-test-utils@0.6.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-react@1.12.0-next.1 + +### Patch Changes + +- c2cbe1e: Updated dependency `use-immer` to `^0.10.0`. +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.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 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-search@1.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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 + - @backstage/plugin-search-react@1.8.0-next.1 + +## @backstage/plugin-search-backend@1.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-search-backend-node@1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-react@1.8.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-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 + +## @backstage/plugin-signals@0.0.10-next.1 + +### Patch Changes + +- 3e9b1a4: Put a name on the `SignalsDisplay` component extension +- Updated dependencies + - @backstage/core-components@0.14.11-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.5-next.0 + +## @backstage/plugin-signals-backend@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-signals-node@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-techdocs@1.10.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-backend@1.10.13-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-node@1.12.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @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.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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.5-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.101-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.1 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + - @backstage/plugin-signals@0.0.10-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.9-next.1 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-import@0.12.3-next.1 + - @backstage/plugin-devtools@0.1.18-next.1 + - @backstage/plugin-home@0.7.10-next.1 + - @backstage/plugin-kubernetes@0.11.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + - @backstage/plugin-notifications@0.3.1-next.1 + - @backstage/plugin-org@0.6.29-next.1 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder@1.25.0-next.1 + - @backstage/plugin-search@1.4.16-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + - @backstage/plugin-user-settings@0.8.12-next.1 + +## example-app-next@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.1 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + - @backstage/plugin-signals@0.0.10-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.9-next.1 + - @backstage/plugin-app-visualizer@0.1.10-next.1 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-import@0.12.3-next.1 + - @backstage/plugin-home@0.7.10-next.1 + - @backstage/plugin-kubernetes@0.11.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + - @backstage/plugin-notifications@0.3.1-next.1 + - @backstage/plugin-org@0.6.29-next.1 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder@1.25.0-next.1 + - @backstage/plugin-search@1.4.16-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + - @backstage/plugin-user-settings@0.8.12-next.1 + +## app-next-example-plugin@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + +## example-backend@0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-notifications-backend@0.4.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + +## example-backend-legacy@0.2.102-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## e2e-test@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @internal/plugin-todo-list@1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index a6652bd036..913a71f514 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -326,7 +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 `package.json` files when necessary. +in the package registry, and will update entries `package.json` files when necessary. See more how this command can be configured and used [for keeping Backstage updated](../../getting-started/keeping-backstage-updated.md). ```text Usage: backstage-cli versions:bump [options] diff --git a/docs/tooling/local-dev/profiling.md b/docs/tooling/local-dev/profiling.md new file mode 100644 index 0000000000..bbc263c3d5 --- /dev/null +++ b/docs/tooling/local-dev/profiling.md @@ -0,0 +1,65 @@ +--- +id: profiling +title: Profiling Backstage +description: Finding performance bottlenecks in your Backstage application +--- + +Profiling can help you find performance bottlenecks in your code. This guide will show you how to profile +both the backend and frontend of your Backstage application. + +# Backend + +To profile the backend, start the backend with the `--inspect` flag: + +```shell +yarn workspace backend start --inspect +# or `yarn workspace example-backend start --inspect` in this repository +``` + +Next you can use the Chrome DevTools to profile the backend by navigating to `chrome://inspect` and +clicking the `Open dedicated DevTools for Node` link. + +In the `Performance` tab, you can start a new recording by clicking the `Start` button. After you +have recorded some data, you can stop the recording by clicking the `Stop` button. The recording +will show you a flame graph of the backend's execution, which can help you identify performance issues. + +You can also use the `Memory` tab to profile the backend's memory usage and find potential memory leaks. + +It's recommended to start profiling with short periods of time to avoid too much data being collected. + +## Stress testing + +To get more out of profiling, you might want to introduce additional load to your application with some tooling. +One such tool is called [AutoCannon](https://www.npmjs.com/package/autocannon) which can be used to stress test the +API endpoints of your application. You can install it globally with `npm install -g autocannon`. + +To be able to access the API endpoints, you must configure a static token for the backend. In your +`app-config.yaml` file, add the following configuration: + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: autocannon12345 + subject: autocannon +``` + +See more information in the [Service to Service Auth](../../auth/service-to-service-auth.md) documentation. +To run the stress test, you can use the following command: + +```shell +autocannon -H "Authorization=Bearer autocannon12345" http://localhost:7007/api/catalog/entities +``` + +See more command options in the AutoCannon documentation. + +# Frontend + +Profiling the frontend can be done by using the `React DevTools` extension for Chrome or Firefox. +The extension is available for download from the Chrome Web Store or the Firefox Add-ons website. + +To start profiling, start the application with `yarn dev` and open inspector in the browser. In the +`Profiler` tab (far to the right), click the `Start profiling` button to start recording. After +you have recorded some data by navigating through the page, click the `Stop profiling` button to stop the recording. 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/enable-public-entry.md b/docs/tutorials/enable-public-entry.md index 25d7382f13..1ba4983618 100644 --- a/docs/tutorials/enable-public-entry.md +++ b/docs/tutorials/enable-public-entry.md @@ -106,40 +106,12 @@ That's it! 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'; +import { signInPageModule } from './overrides/SignInPage'; +import { createPublicSignInApp } from '@backstage/frontend-defaults'; -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], - }), - ], +const app = createPublicSignInApp({ + features: [signInPageModule], }); 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/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/data/plugins/ai-assistant.yaml b/microsite/data/plugins/ai-assistant.yaml index 997846c14b..6ac4b6cd9e 100644 --- a/microsite/data/plugins/ai-assistant.yaml +++ b/microsite/data/plugins/ai-assistant.yaml @@ -1,6 +1,6 @@ --- title: AI Assistant - RAG AI -author: RoadieHQ +author: roadie.io authorUrl: https://roadie.io category: Search description: Query LLMs with contextual information from your catalog. diff --git a/microsite/data/plugins/analytics-module-generic.yaml b/microsite/data/plugins/analytics-module-generic.yaml new file mode 100644 index 0000000000..260319aca6 --- /dev/null +++ b/microsite/data/plugins/analytics-module-generic.yaml @@ -0,0 +1,9 @@ +--- +title: 'Analytics Module: Generic HTTP' +author: Josephine Pfeiffer +authorUrl: https://josie.lol +category: Monitoring +description: Track usage of your Backstage instance using generic POST endpoints. +documentation: https://github.com/pfeifferj/backstage-plugin-analytics-generic/blob/main/README.md +npmPackageName: '@pfeifferj/backstage-plugin-analytics-generic' +addedDate: '2024-08-23' diff --git a/microsite/data/plugins/aws-app-development.yaml b/microsite/data/plugins/aws-app-development.yaml index 47b40b8b3b..5df0598c5e 100644 --- a/microsite/data/plugins/aws-app-development.yaml +++ b/microsite/data/plugins/aws-app-development.yaml @@ -1,10 +1,10 @@ --- -title: OPA on AWS +title: Harmonix on AWS author: Amazon Web Services authorUrl: https://aws.amazon.com/ category: Infrastructure -description: Orchestrate Platforms and Applications (OPA) allows customers to build and manage AWS Apps & Environments within Backstage -documentation: https://opaonaws.io -iconUrl: https://github.com/awslabs/app-development-for-backstage-io-on-aws/blob/main/website/static/img/white_OPA_text02.png?raw=true +description: Harmonix allows customers to build and manage AWS Apps & Environments within Backstage +documentation: https://harmonixonaws.io +iconUrl: https://github.com/awslabs/harmonix/blob/main/docs/images/harmonix-white.png?raw=true npmPackageName: '@aws/plugin-aws-apps-for-backstage' addedDate: '2023-05-10' 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/gravatar.yaml b/microsite/data/plugins/gravatar.yaml new file mode 100644 index 0000000000..d31c16a155 --- /dev/null +++ b/microsite/data/plugins/gravatar.yaml @@ -0,0 +1,14 @@ +--- +title: Gravatar catalog processor +author: roadie.io +authorUrl: https://roadie.io +category: Catalog +description: The processor generates a Gravatar URL for each user based on their email address and the generated Gravatar URL is added to the spec.profile.picture field of User entities. +documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/backend/catalog-backend-module-gravatar/README.md +iconUrl: https://gravatar.com/avatar/gravatar +npmPackageName: '@roadiehq/catalog-backend-module-gravatar' +tags: + - gravatar + - catalog + - processor +addedDate: '2024-08-26' diff --git a/microsite/data/plugins/hashicorp-terraform.yaml b/microsite/data/plugins/hashicorp-terraform.yaml new file mode 100644 index 0000000000..c254667015 --- /dev/null +++ b/microsite/data/plugins/hashicorp-terraform.yaml @@ -0,0 +1,10 @@ +--- +title: Hashicorp Terraform +author: GlobalLogic UK&I +authorUrl: https://www.globallogic.com +category: Infrastructure +description: Easily view Hashicorp Terraform runs for a specific workspace. +documentation: https://github.com/globallogicuki/globallogic-backstage-plugins/tree/main/plugins/terraform +iconUrl: /img/terraform-logo.svg +npmPackageName: '@globallogicuki/backstage-plugin-terraform' +addedDate: '2024-07-17' diff --git a/microsite/data/plugins/kubelog.yaml b/microsite/data/plugins/kubelog.yaml new file mode 100644 index 0000000000..fcadba2978 --- /dev/null +++ b/microsite/data/plugins/kubelog.yaml @@ -0,0 +1,10 @@ +--- +title: Kubelog +author: Julio Fernandez +authorUrl: https://github.com/jfvilas +category: Observability +description: Kubelog provides your Backstage users the ability to view logs of your Kubernetes objects linked to Backstage entities. Security controls can be applied. +documentation: https://github.com/jfvilas/kubelog +iconUrl: https://raw.githubusercontent.com/jfvilas/kubelog/master/src/assets/kubelog-logo.png +npmPackageName: '@jfvilas/plugin-kubelog' +addedDate: 2024-08-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/launchdarkly.yaml b/microsite/data/plugins/launchdarkly.yaml new file mode 100644 index 0000000000..30e4cab685 --- /dev/null +++ b/microsite/data/plugins/launchdarkly.yaml @@ -0,0 +1,9 @@ +--- +title: LaunchDarkly +author: roadie.io +authorUrl: https://roadie.io +category: CI/CD +description: View LaunchDarkly feature flags for your entities in Backstage. +documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/launchdarkly/plugins/frontend/backstage-plugin-launchdarkly/README.md +npmPackageName: '@roadiehq/backstage-plugin-launchdarkly' +addedDate: '2024-07-27' 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/docusaurus.config.ts b/microsite/docusaurus.config.ts index 67f6de4962..a6909f139f 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -195,6 +195,10 @@ const config: Config = { from: '/docs/local-dev/debugging/', to: '/docs/tooling/local-dev/debugging', }, + { + from: '/docs/plugins/url-reader/', + to: '/docs/backend-system/core-services/url-reader', + }, ], }, ], @@ -241,7 +245,7 @@ const config: Config = { position: 'left', }, { - to: 'docs/releases/v1.29.0', + to: 'docs/releases/v1.30.0', label: 'Releases', position: 'left', }, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ddccb14c91..6038229bf0 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.30.0", "releases/v1.29.0", "releases/v1.28.0", "releases/v1.27.0", @@ -134,6 +135,7 @@ "features/search/concepts", "features/search/architecture", "features/search/search-engines", + "features/search/collators", "features/search/how-to-guides" ] }, @@ -272,8 +274,7 @@ "items": [ "plugins/proxying", "plugins/backend-plugin", - "plugins/call-existing-api", - "plugins/url-reader" + "plugins/call-existing-api" ] }, { @@ -365,7 +366,8 @@ "label": "Local Development", "items": [ "tooling/local-dev/linking-local-packages", - "tooling/local-dev/debugging" + "tooling/local-dev/debugging", + "tooling/local-dev/profiling" ] }, "tooling/package-metadata" @@ -452,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" ] }, { @@ -467,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" ] @@ -479,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/static/img/terraform-logo.svg b/microsite/static/img/terraform-logo.svg new file mode 100644 index 0000000000..9ec37bdab4 --- /dev/null +++ b/microsite/static/img/terraform-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 00732d79f2..d5d78e9371 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -197,118 +197,117 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": - version: 7.23.5 - resolution: "@babel/code-frame@npm:7.23.5" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.8.3": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" dependencies: - "@babel/highlight": ^7.23.4 - chalk: ^2.4.2 - checksum: d90981fdf56a2824a9b14d19a4c0e8db93633fd488c772624b4e83e0ceac6039a27cd298a247c3214faa952bf803ba23696172ae7e7235f3b97f43ba278c569a + "@babel/highlight": ^7.24.7 + picocolors: ^1.0.0 + checksum: 830e62cd38775fdf84d612544251ce773d544a8e63df667728cc9e0126eeef14c6ebda79be0f0bc307e8318316b7f58c27ce86702e0a1f5c321d842eb38ffda4 languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.20, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9, @babel/compat-data@npm:^7.23.5": - version: 7.23.5 - resolution: "@babel/compat-data@npm:7.23.5" - checksum: 06ce244cda5763295a0ea924728c09bae57d35713b675175227278896946f922a63edf803c322f855a3878323d48d0255a2a3023409d2a123483c8a69ebb4744 +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/compat-data@npm:7.25.4" + checksum: b12a91d27c3731a4b0bdc9312a50b1911f41f7f728aaf0d4b32486e2257fd2cb2d3ea1a295e98449600c48f2c7883a3196ca77cda1cef7d97a10c2e83d037974 languageName: node linkType: hard -"@babel/core@npm:^7.19.6, @babel/core@npm:^7.23.3": - version: 7.23.9 - resolution: "@babel/core@npm:7.23.9" +"@babel/core@npm:^7.21.3, @babel/core@npm:^7.23.3": + version: 7.25.2 + resolution: "@babel/core@npm:7.25.2" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.23.5 - "@babel/generator": ^7.23.6 - "@babel/helper-compilation-targets": ^7.23.6 - "@babel/helper-module-transforms": ^7.23.3 - "@babel/helpers": ^7.23.9 - "@babel/parser": ^7.23.9 - "@babel/template": ^7.23.9 - "@babel/traverse": ^7.23.9 - "@babel/types": ^7.23.9 + "@babel/code-frame": ^7.24.7 + "@babel/generator": ^7.25.0 + "@babel/helper-compilation-targets": ^7.25.2 + "@babel/helper-module-transforms": ^7.25.2 + "@babel/helpers": ^7.25.0 + "@babel/parser": ^7.25.0 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.2 + "@babel/types": ^7.25.2 convert-source-map: ^2.0.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.3 semver: ^6.3.1 - checksum: 634a511f74db52a5f5a283c1121f25e2227b006c095b84a02a40a9213842489cd82dc7d61cdc74e10b5bcd9bb0a4e28bab47635b54c7e2256d47ab57356e2a76 + checksum: 9a1ef604a7eb62195f70f9370cec45472a08114e3934e3eaaedee8fd754edf0730e62347c7b4b5e67d743ce57b5bb8cf3b92459482ca94d06e06246ef021390a languageName: node linkType: hard -"@babel/generator@npm:^7.23.3, @babel/generator@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/generator@npm:7.23.6" +"@babel/generator@npm:^7.23.3, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4": + version: 7.25.5 + resolution: "@babel/generator@npm:7.25.5" dependencies: - "@babel/types": ^7.23.6 - "@jridgewell/gen-mapping": ^0.3.2 - "@jridgewell/trace-mapping": ^0.3.17 + "@babel/types": ^7.25.4 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 jsesc: ^2.5.1 - checksum: 1a1a1c4eac210f174cd108d479464d053930a812798e09fee069377de39a893422df5b5b146199ead7239ae6d3a04697b45fc9ac6e38e0f6b76374390f91fc6c + checksum: d7713f02536a8144eca810e9b13ae854b05fec462348eaf52e7b50df2c0a312bc43bfff0e8e10d6dd982e8986d61175ac8e67d7358a8b4dad9db4d6733bf0c9c languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" +"@babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" dependencies: - "@babel/types": ^7.22.5 - checksum: 53da330f1835c46f26b7bf4da31f7a496dee9fd8696cca12366b94ba19d97421ce519a74a837f687749318f94d1a37f8d1abcbf35e8ed22c32d16373b2f6198d + "@babel/types": ^7.24.7 + checksum: 6178566099a6a0657db7a7fa601a54fb4731ca0b8614fbdccfd8e523c210c13963649bc8fdfd53ce7dd14d05e3dda2fb22dea5b30113c488b9eb1a906d60212e languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.15" +"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7" dependencies: - "@babel/types": ^7.22.15 - checksum: 639c697a1c729f9fafa2dd4c9af2e18568190299b5907bd4c2d0bc818fcbd1e83ffeecc2af24327a7faa7ac4c34edd9d7940510a5e66296c19bad17001cf5c7a + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: 71a6158a9fdebffb82fdc400d5555ba8f2e370cea81a0d578155877bdc4db7d5252b75c43b2fdf3f72b3f68348891f99bd35ae315542daad1b7ace8322b1abcb languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.5, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/helper-compilation-targets@npm:7.23.6" +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-compilation-targets@npm:7.25.2" dependencies: - "@babel/compat-data": ^7.23.5 - "@babel/helper-validator-option": ^7.23.5 - browserslist: ^4.22.2 + "@babel/compat-data": ^7.25.2 + "@babel/helper-validator-option": ^7.24.8 + browserslist: ^4.23.1 lru-cache: ^5.1.1 semver: ^6.3.1 - checksum: c630b98d4527ac8fe2c58d9a06e785dfb2b73ec71b7c4f2ddf90f814b5f75b547f3c015f110a010fd31f76e3864daaf09f3adcd2f6acdbfb18a8de3a48717590 + checksum: aed33c5496cb9db4b5e2d44e26bf8bc474074cc7f7bb5ebe1d4a20fdeb362cb3ba9e1596ca18c7484bcd6e5c3a155ab975e420d520c0ae60df81f9de04d0fd16 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.22.11, @babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/helper-create-class-features-plugin@npm:7.22.15" +"@babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 - "@babel/helper-member-expression-to-functions": ^7.22.15 - "@babel/helper-optimise-call-expression": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.9 - "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 - "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-annotate-as-pure": ^7.24.7 + "@babel/helper-member-expression-to-functions": ^7.24.8 + "@babel/helper-optimise-call-expression": ^7.24.7 + "@babel/helper-replace-supers": ^7.25.0 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 + "@babel/traverse": ^7.25.4 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: 52c500d8d164abb3a360b1b7c4b8fff77bc4a5920d3a2b41ae6e1d30617b0dc0b972c1f5db35b1752007e04a748908b4a99bc872b73549ae837e87dcdde005a3 + checksum: 4544ebda4516eb25efdebd47ca024bd7bdb1eb6e7cc3ad89688c8ef8e889734c2f4411ed78981899c641394f013f246f2af63d92a0e9270f6c453309b4cb89ba languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.15" +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 + "@babel/helper-annotate-as-pure": ^7.24.7 regexpu-core: ^5.3.1 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: 0243b8d4854f1dc8861b1029a46d3f6393ad72f366a5a08e36a4648aa682044f06da4c6e87a456260e1e1b33c999f898ba591a0760842c1387bcc93fbf2151a6 + checksum: df55fdc6a1f3090dd37d91347df52d9322d52affa239543808dc142f8fe35e6787e67d8612337668198fac85826fafa9e6772e6c28b7d249ec94e6fafae5da6e languageName: node linkType: hard @@ -327,218 +326,238 @@ __metadata: languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.22.20, @babel/helper-environment-visitor@npm:^7.22.5": - version: 7.22.20 - resolution: "@babel/helper-environment-visitor@npm:7.22.20" - checksum: d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69 +"@babel/helper-define-polyfill-provider@npm:^0.6.2": + version: 0.6.2 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" + dependencies: + "@babel/helper-compilation-targets": ^7.22.6 + "@babel/helper-plugin-utils": ^7.22.5 + debug: ^4.1.1 + lodash.debounce: ^4.0.8 + resolve: ^1.14.2 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 2bba965ea9a4887ddf9c11d51d740ab473bd7597b787d042c325f6a45912dfe908c2d6bb1d837bf82f7e9fa51e6ad5150563c58131d2bb85515e63d971414a9c languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.22.5, @babel/helper-function-name@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-function-name@npm:7.23.0" +"@babel/helper-member-expression-to-functions@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" dependencies: - "@babel/template": ^7.22.15 - "@babel/types": ^7.23.0 - checksum: e44542257b2d4634a1f979244eb2a4ad8e6d75eb6761b4cfceb56b562f7db150d134bc538c8e6adca3783e3bc31be949071527aa8e3aab7867d1ad2d84a26e10 + "@babel/traverse": ^7.24.8 + "@babel/types": ^7.24.8 + checksum: bf923d05d81b06857f4ca4fe9c528c9c447a58db5ea39595bb559eae2fce01a8266173db0fd6a2ec129d7bbbb9bb22f4e90008252f7c66b422c76630a878a4bc languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-hoist-variables@npm:7.22.5" +"@babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-imports@npm:7.24.7" dependencies: - "@babel/types": ^7.22.5 - checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: 8ac15d96d262b8940bc469052a048e06430bba1296369be695fabdf6799f201dd0b00151762b56012a218464e706bc033f27c07f6cec20c6f8f5fd6543c67054 languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.22.15": - version: 7.23.0 - resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" +"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-module-transforms@npm:7.25.2" dependencies: - "@babel/types": ^7.23.0 - checksum: 494659361370c979ada711ca685e2efe9460683c36db1b283b446122596602c901e291e09f2f980ecedfe6e0f2bd5386cb59768285446530df10c14df1024e75 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/helper-module-imports@npm:7.22.15" - dependencies: - "@babel/types": ^7.22.15 - checksum: ecd7e457df0a46f889228f943ef9b4a47d485d82e030676767e6a2fdcbdaa63594d8124d4b55fd160b41c201025aec01fc27580352b1c87a37c9c6f33d116702 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.22.5, @babel/helper-module-transforms@npm:^7.23.0, @babel/helper-module-transforms@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/helper-module-transforms@npm:7.23.3" - dependencies: - "@babel/helper-environment-visitor": ^7.22.20 - "@babel/helper-module-imports": ^7.22.15 - "@babel/helper-simple-access": ^7.22.5 - "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/helper-validator-identifier": ^7.22.20 + "@babel/helper-module-imports": ^7.24.7 + "@babel/helper-simple-access": ^7.24.7 + "@babel/helper-validator-identifier": ^7.24.7 + "@babel/traverse": ^7.25.2 peerDependencies: "@babel/core": ^7.0.0 - checksum: 5d0895cfba0e16ae16f3aa92fee108517023ad89a855289c4eb1d46f7aef4519adf8e6f971e1d55ac20c5461610e17213f1144097a8f932e768a9132e2278d71 + checksum: 282d4e3308df6746289e46e9c39a0870819630af5f84d632559171e4fae6045684d771a65f62df3d569e88ccf81dc2def78b8338a449ae3a94bb421aa14fc367 languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" +"@babel/helper-optimise-call-expression@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" dependencies: - "@babel/types": ^7.22.5 - checksum: c70ef6cc6b6ed32eeeec4482127e8be5451d0e5282d5495d5d569d39eb04d7f1d66ec99b327f45d1d5842a9ad8c22d48567e93fc502003a47de78d122e355f7c + "@babel/types": ^7.24.7 + checksum: 280654eaf90e92bf383d7eed49019573fb35a98c9e992668f701ad099957246721044be2068cf6840cb2299e0ad393705a1981c88c23a1048096a8d59e5f79a3 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.22.5 - resolution: "@babel/helper-plugin-utils@npm:7.22.5" - checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.24.8 + resolution: "@babel/helper-plugin-utils@npm:7.24.8" + checksum: 73b1a83ba8bcee21dc94de2eb7323207391715e4369fd55844bb15cf13e3df6f3d13a40786d990e6370bf0f571d94fc31f70dec96c1d1002058258c35ca3767a languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.22.5, @babel/helper-remap-async-to-generator@npm:^7.22.9": - version: 7.22.20 - resolution: "@babel/helper-remap-async-to-generator@npm:7.22.20" +"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-environment-visitor": ^7.22.20 - "@babel/helper-wrap-function": ^7.22.20 + "@babel/helper-annotate-as-pure": ^7.24.7 + "@babel/helper-wrap-function": ^7.25.0 + "@babel/traverse": ^7.25.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 2fe6300a6f1b58211dffa0aed1b45d4958506d096543663dba83bd9251fe8d670fa909143a65b45e72acb49e7e20fbdb73eae315d9ddaced467948c3329986e7 + checksum: 47f3065e43fe9d6128ddb4291ffb9cf031935379265fd13de972b5f241943121f7583efb69cd2e1ecf39e3d0f76f047547d56c3fcc2c853b326fad5465da0bd7 languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.22.5, @babel/helper-replace-supers@npm:^7.22.9": - version: 7.22.20 - resolution: "@babel/helper-replace-supers@npm:7.22.20" +"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-replace-supers@npm:7.25.0" dependencies: - "@babel/helper-environment-visitor": ^7.22.20 - "@babel/helper-member-expression-to-functions": ^7.22.15 - "@babel/helper-optimise-call-expression": ^7.22.5 + "@babel/helper-member-expression-to-functions": ^7.24.8 + "@babel/helper-optimise-call-expression": ^7.24.7 + "@babel/traverse": ^7.25.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: a0008332e24daedea2e9498733e3c39b389d6d4512637e000f96f62b797e702ee24a407ccbcd7a236a551590a38f31282829a8ef35c50a3c0457d88218cae639 + checksum: f669fc2487c22d40b808f94b9c3ee41129484d5ef0ba689bdd70f216ff91e10b6b021d2f8cd37e7bdd700235a2a6ae6622526344f064528190383bf661ac65f8 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-simple-access@npm:7.22.5" +"@babel/helper-simple-access@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-simple-access@npm:7.24.7" dependencies: - "@babel/types": ^7.22.5 - checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2 + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: ddbf55f9dea1900213f2a1a8500fabfd21c5a20f44dcfa957e4b0d8638c730f88751c77f678644f754f1a1dc73f4eb8b766c300deb45a9daad000e4247957819 languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" dependencies: - "@babel/types": ^7.22.5 - checksum: 1012ef2295eb12dc073f2b9edf3425661e9b8432a3387e62a8bc27c42963f1f216ab3124228015c748770b2257b4f1fda882ca8fa34c0bf485e929ae5bc45244 + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: 11b28fe534ce2b1a67c4d8e51a7b5711a2a0a0cae802f74614eee54cca58c744d9a62f6f60103c41759e81c537d270bfd665bf368a6bea214c6052f2094f8407 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.22.6": - version: 7.22.6 - resolution: "@babel/helper-split-export-declaration@npm:7.22.6" +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 39b03c5119216883878655b149148dc4d2e284791e969b19467a9411fccaa33f7a713add98f4db5ed519535f70ad273cdadfd2eb54d47ebbdeac5083351328ce + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 6799ab117cefc0ecd35cd0b40ead320c621a298ecac88686a14cffceaac89d80cdb3c178f969861bf5fa5e4f766648f9161ea0752ecfe080d8e89e3147270257 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.22.15, @babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c + languageName: node + linkType: hard + +"@babel/helper-wrap-function@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-wrap-function@npm:7.25.0" dependencies: - "@babel/types": ^7.22.5 - checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.0 + "@babel/types": ^7.25.0 + checksum: 0095b4741704066d1687f9bbd5370bb88c733919e4275e49615f70c180208148ff5f24ab58d186ce92f8f5d28eab034ec6617e9264590cc4744c75302857629c languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/helper-string-parser@npm:7.23.4" - checksum: c0641144cf1a7e7dc93f3d5f16d5327465b6cf5d036b48be61ecba41e1eece161b48f46b7f960951b67f8c3533ce506b16dece576baef4d8b3b49f8c65410f90 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-validator-identifier@npm:7.22.20" - checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.22.15, @babel/helper-validator-option@npm:^7.23.5": - version: 7.23.5 - resolution: "@babel/helper-validator-option@npm:7.23.5" - checksum: 537cde2330a8aede223552510e8a13e9c1c8798afee3757995a7d4acae564124fe2bf7e7c3d90d62d3657434a74340a274b3b3b1c6f17e9a2be1f48af29cb09e - languageName: node - linkType: hard - -"@babel/helper-wrap-function@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-wrap-function@npm:7.22.20" +"@babel/helpers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helpers@npm:7.25.0" dependencies: - "@babel/helper-function-name": ^7.22.5 - "@babel/template": ^7.22.15 - "@babel/types": ^7.22.19 - checksum: 221ed9b5572612aeb571e4ce6a256f2dee85b3c9536f1dd5e611b0255e5f59a3d0ec392d8d46d4152149156a8109f92f20379b1d6d36abb613176e0e33f05fca + "@babel/template": ^7.25.0 + "@babel/types": ^7.25.0 + checksum: 739e3704ff41a30f5eaac469b553f4d3ab02be6ced083f5925851532dfbd9efc5c347728e77b754ed0b262a4e5e384e60932a62c192d338db7e4b7f3adf9f4a7 languageName: node linkType: hard -"@babel/helpers@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/helpers@npm:7.23.9" +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" dependencies: - "@babel/template": ^7.23.9 - "@babel/traverse": ^7.23.9 - "@babel/types": ^7.23.9 - checksum: 2678231192c0471dbc2fc403fb19456cc46b1afefcfebf6bc0f48b2e938fdb0fef2e0fe90c8c8ae1f021dae5012b700372e4b5d15867f1d7764616532e4a6324 - languageName: node - linkType: hard - -"@babel/highlight@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/highlight@npm:7.23.4" - dependencies: - "@babel/helper-validator-identifier": ^7.22.20 + "@babel/helper-validator-identifier": ^7.24.7 chalk: ^2.4.2 js-tokens: ^4.0.0 - checksum: 643acecdc235f87d925979a979b539a5d7d1f31ae7db8d89047269082694122d11aa85351304c9c978ceeb6d250591ccadb06c366f358ccee08bb9c122476b89 + picocolors: ^1.0.0 + checksum: 5cd3a89f143671c4ac129960024ba678b669e6fc673ce078030f5175002d1d3d52bc10b22c5b916a6faf644b5028e9a4bd2bb264d053d9b05b6a98690f1d46f1 languageName: node linkType: hard -"@babel/parser@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/parser@npm:7.23.9" +"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/parser@npm:7.25.4" + dependencies: + "@babel/types": ^7.25.4 bin: parser: ./bin/babel-parser.js - checksum: e7cd4960ac8671774e13803349da88d512f9292d7baa952173260d3e8f15620a28a3701f14f709d769209022f9e7b79965256b8be204fc550cfe783cdcabe7c7 + checksum: fe4f083d4ad34f019dd7fad672cd007003004fb0a3df9b7315a5da9a5e8e56c1fed95acab6862e7d76cfccb2e8e364bcc307e9117718e6bb6dfb2e87ad065abf languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.15" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3": + version: 7.25.3 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/traverse": ^7.25.3 peerDependencies: "@babel/core": ^7.0.0 - checksum: 8910ca21a7ec7c06f7b247d4b86c97c5aa15ef321518f44f6f490c5912fdf82c605aaa02b90892e375d82ccbedeadfdeadd922c1b836c9dd4c596871bf654753 + checksum: d3dba60f360defe70eb43e35a1b17ea9dd4a99e734249e15be3d5c288019644f96f88d7ff51990118fda0845b4ad50f6d869e0382232b1d8b054d113d4eea7e2 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.15" +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 - "@babel/plugin-transform-optional-chaining": ^7.22.15 + "@babel/helper-plugin-utils": ^7.24.8 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: fd56d1e6435f2c008ca9050ea906ff7eedcbec43f532f2bf2e7e905d8bf75bf5e4295ea9593f060394e2c8e45737266ccbf718050bad2dd7be4e7613c60d1b5b + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" + dependencies: + "@babel/helper-plugin-utils": ^7.24.8 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 13ed301b108d85867d64226bbc4032b07dd1a23aab68e9e32452c4fe3930f2198bb65bdae9c262c4104bd5e45647bc1830d25d43d356ee9a137edd8d5fab8350 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 + "@babel/plugin-transform-optional-chaining": ^7.24.7 peerDependencies: "@babel/core": ^7.13.0 - checksum: fbefedc0da014c37f1a50a8094ce7dbbf2181ae93243f23d6ecba2499b5b20196c2124d6a4dfe3e9e0125798e80593103e456352a4beb4e5c6f7c75efb80fdac + checksum: 07b92878ac58a98ea1fdf6a8b4ec3413ba4fa66924e28b694d63ec5b84463123fbf4d7153b56cf3cedfef4a3482c082fe3243c04f8fb2c041b32b0e29b4a9e21 + languageName: node + linkType: hard + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" + dependencies: + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/traverse": ^7.25.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: c8d08b8d6cc71451ad2a50cf7db72ab5b41c1e5e2e4d56cf6837a25a61270abd682c6b8881ab025f11a552d2024b3780519bb051459ebb71c27aed13d9917663 languageName: node linkType: hard @@ -606,25 +625,25 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" +"@babel/plugin-syntax-import-assertions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2b8b5572db04a7bef1e6cd20debf447e4eef7cb012616f5eceb8fa3e23ce469b8f76ee74fd6d1e158ba17a8f58b0aec579d092fb67c5a30e83ccfbc5754916c1 + checksum: c4d67be4eb1d4637e361477dbe01f5b392b037d17c1f861cfa0faa120030e137aab90a9237931b8040fd31d1e5d159e11866fa1165f78beef7a3be876a391a17 languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.22.5" +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 197b3c5ea2a9649347f033342cb222ab47f4645633695205c0250c6bf2af29e643753b8bb24a2db39948bef08e7c540babfd365591eb57fc110cb30b425ffc47 + checksum: 590dbb5d1a15264f74670b427b8d18527672c3d6c91d7bae7e65f80fd810edbc83d90e68065088644cbad3f2457ed265a54a9956fb789fcb9a5b521822b3a275 languageName: node linkType: hard @@ -650,14 +669,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" +"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8829d30c2617ab31393d99cec2978e41f014f4ac6f01a1cecf4c4dd8320c3ec12fdc3ce121126b2d8d32f6887e99ca1a0bad53dedb1e6ad165640b92b24980ce + checksum: 7a5ca629d8ca1e1ee78705a78e58c12920d07ed8006d7e7232b31296a384ff5e41d7b649bde5561196041037bbb9f9715be1d1c20975df87ca204f34ad15b965 languageName: node linkType: hard @@ -749,14 +768,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" +"@babel/plugin-syntax-typescript@npm:^7.24.7": + version: 7.25.4 + resolution: "@babel/plugin-syntax-typescript@npm:7.25.4" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8ab7718fbb026d64da93681a57797d60326097fd7cb930380c8bffd9eb101689e90142c760a14b51e8e69c88a73ba3da956cb4520a3b0c65743aee5c71ef360a + checksum: 9b89b8930cd5983f64251d75c9fcdc17a8dc73837d6de12220ff972888ecff4054a6467cf0c423cad242aa96c0f0564a39a0823073728cc02239b80d13f02230 languageName: node linkType: hard @@ -772,468 +791,477 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.22.5" +"@babel/plugin-transform-arrow-functions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 35abb6c57062802c7ce8bd96b2ef2883e3124370c688bbd67609f7d2453802fb73944df8808f893b6c67de978eb2bcf87bbfe325e46d6f39b5fcb09ece11d01a + checksum: 707c209b5331c7dc79bd326128c6a6640dbd62a78da1653c844db20c4f36bf7b68454f1bc4d2d051b3fde9136fa291f276ec03a071bb00ee653069ff82f91010 languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.22.15" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" dependencies: - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-remap-async-to-generator": ^7.22.9 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-remap-async-to-generator": ^7.25.0 "@babel/plugin-syntax-async-generators": ^7.8.4 + "@babel/traverse": ^7.25.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: fad98786b446ce63bde0d14a221e2617eef5a7bbca62b49d96f16ab5e1694521234cfba6145b830fbf9af16d60a8a3dbf148e8694830bd91796fe333b0599e73 + checksum: 4235444735a1946f8766fe56564a8134c2c36c73e6cf83b3f2ed5624ebc84ff5979506a6a5b39acdb23aa09d442a6af471710ed408ccce533a2c4d2990b9df6a languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" +"@babel/plugin-transform-async-to-generator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7" dependencies: - "@babel/helper-module-imports": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-remap-async-to-generator": ^7.22.5 + "@babel/helper-module-imports": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-remap-async-to-generator": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b95f23f99dcb379a9f0a1c2a3bbea3f8dc0e1b16dc1ac8b484fe378370169290a7a63d520959a9ba1232837cf74a80e23f6facbe14fd42a3cda6d3c2d7168e62 + checksum: 13704fb3b83effc868db2b71bfb2c77b895c56cb891954fc362e95e200afd523313b0e7cf04ce02f45b05e76017c5b5fa8070c92613727a35131bb542c253a36 languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" +"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 416b1341858e8ca4e524dee66044735956ced5f478b2c3b9bc11ec2285b0c25d7dbb96d79887169eb938084c95d0a89338c8b2fe70d473bd9dc92e5d9db1732c + checksum: 249cdcbff4e778b177245f9652b014ea4f3cd245d83297f10a7bf6d97790074089aa62bcde8c08eb299c5e68f2faed346b587d3ebac44d625ba9a83a4ee27028 languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.22.15": - version: 7.23.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.23.0" +"@babel/plugin-transform-block-scoping@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0cfe925cc3b5a3ad407e2253fab3ceeaa117a4b291c9cb245578880872999bca91bd83ffa0128ae9ca356330702e1ef1dcb26804f28d2cef678239caf629f73e + checksum: b1a8f932f69ad2a47ae3e02b4cedd2a876bfc2ac9cf72a503fd706cdc87272646fe9eed81e068c0fc639647033de29f7fa0c21cddd1da0026f83dbaac97316a8 languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-class-properties@npm:7.22.5" +"@babel/plugin-transform-class-properties@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-class-features-plugin": ^7.25.4 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b830152dfc2ff2f647f0abe76e6251babdfbef54d18c4b2c73a6bf76b1a00050a5d998dac80dc901a48514e95604324943a9dd39317073fe0928b559e0e0c579 + checksum: b73f7d968639c6c2dfc13f4c5a8fe45cefd260f0faa7890ae12e65d41211072544ff5e128c8b61a86887b29ffd3df8422dbdfbf61648488e71d4bb599c41f4a5 languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-class-static-block@npm:7.22.11" +"@babel/plugin-transform-class-static-block@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7" dependencies: - "@babel/helper-create-class-features-plugin": ^7.22.11 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-class-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-class-static-block": ^7.14.5 peerDependencies: "@babel/core": ^7.12.0 - checksum: 69f040506fad66f1c6918d288d0e0edbc5c8a07c8b4462c1184ad2f9f08995d68b057126c213871c0853ae0c72afc60ec87492049dfacb20902e32346a448bcb + checksum: 324049263504f18416f1c3e24033baebfafd05480fdd885c8ebe6f2b415b0fc8e0b98d719360f9e30743cc78ac387fabc0b3c6606d2b54135756ffb92963b382 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-classes@npm:7.22.15" +"@babel/plugin-transform-classes@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-classes@npm:7.25.4" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-compilation-targets": ^7.22.15 - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 - "@babel/helper-optimise-call-expression": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.9 - "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-annotate-as-pure": ^7.24.7 + "@babel/helper-compilation-targets": ^7.25.2 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-replace-supers": ^7.25.0 + "@babel/traverse": ^7.25.4 globals: ^11.1.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d3f4d0c107dd8a3557ea3575cc777fab27efa92958b41e4a9822f7499725c1f554beae58855de16ddec0a7b694e45f59a26cea8fbde4275563f72f09c6e039a0 + checksum: 0bf20e46eeb691bd60cee5d1b01950fc37accec88018ecace25099f7c8d8509c1ac54d11b8caf9f2157c6945969520642a3bc421159c1a14e80224dc9a7611de languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" +"@babel/plugin-transform-computed-properties@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/template": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/template": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c2a77a0f94ec71efbc569109ec14ea2aa925b333289272ced8b33c6108bdbb02caf01830ffc7e49486b62dec51911924d13f3a76f1149f40daace1898009e131 + checksum: 0cf8c1b1e4ea57dec8d4612460d84fd4cdbf71a7499bb61ee34632cf89018a59eee818ffca88a8d99ee7057c20a4257044d7d463fda6daef9bf1db9fa81563cb languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.22.15": - version: 7.23.0 - resolution: "@babel/plugin-transform-destructuring@npm:7.23.0" +"@babel/plugin-transform-destructuring@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cd6dd454ccc2766be551e4f8a04b1acc2aa539fa19e5c7501c56cc2f8cc921dd41a7ffb78455b4c4b2f954fcab8ca4561ba7c9c7bd5af9f19465243603d18cc3 + checksum: 0b4bd3d608979a1e5bd97d9d42acd5ad405c7fffa61efac4c7afd8e86ea6c2d91ab2d94b6a98d63919571363fe76e0b03c4ff161f0f60241b895842596e4a999 languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" +"@babel/plugin-transform-dotall-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 409b658d11e3082c8f69e9cdef2d96e4d6d11256f005772425fb230cc48fd05945edbfbcb709dab293a1a2f01f9c8a5bb7b4131e632b23264039d9f95864b453 + checksum: 67b10fc6abb1f61f0e765288eb4c6d63d1d0f9fc0660e69f6f2170c56fa16bc74e49857afc644beda112b41771cd90cf52df0940d11e97e52617c77c7dcff171 languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" +"@babel/plugin-transform-duplicate-keys@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: bb1280fbabaab6fab2ede585df34900712698210a3bd413f4df5bae6d8c24be36b496c92722ae676a7a67d060a4624f4d6c23b923485f906bfba8773c69f55b4 + checksum: d1da2ff85ecb56a63f4ccfd9dc9ae69400d85f0dadf44ecddd9e71c6e5c7a9178e74e3a9637555f415a2bb14551e563f09f98534ab54f53d25e8439fdde6ba2d languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.22.11" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.25.0 + "@babel/helper-plugin-utils": ^7.24.8 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 608d6b0e77341189508880fd1a9f605a38d0803dd6f678ea3920ab181b17b377f6d5221ae8cf0104c7a044d30d4ddb0366bd064447695671d78457a656bb264f + languageName: node + linkType: hard + +"@babel/plugin-transform-dynamic-import@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" + dependencies: + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-dynamic-import": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 78fc9c532210bf9e8f231747f542318568ac360ee6c27e80853962c984283c73da3f8f8aebe83c2096090a435b356b092ed85de617a156cbe0729d847632be45 + checksum: 776509ff62ab40c12be814a342fc56a5cc09b91fb63032b2633414b635875fd7da03734657be0f6db2891fe6e3033b75d5ddb6f2baabd1a02e4443754a785002 languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.22.5" +"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7" dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-builder-binary-assignment-operator-visitor": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f2d660c1b1d51ad5fec1cd5ad426a52187204068c4158f8c4aa977b31535c61b66898d532603eef21c15756827be8277f724c869b888d560f26d7fe848bb5eae + checksum: 23c84a23eb56589fdd35a3540f9a1190615be069110a2270865223c03aee3ba4e0fc68fe14850800cf36f0712b26e4964d3026235261f58f0405a29fe8dac9b1 languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.22.11" +"@babel/plugin-transform-export-namespace-from@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-export-namespace-from": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 73af5883a321ed56a4bfd43c8a7de0164faebe619287706896fc6ee2f7a4e69042adaa1338c0b8b4bdb9f7e5fdceb016fb1d40694cb43ca3b8827429e8aac4bf + checksum: 3bd3a10038f10ae0dea1ee42137f3edcf7036b5e9e570a0d1cbd0865f03658990c6c2d84fa2475f87a754e7dc5b46766c16f7ce5c9b32c3040150b6a21233a80 languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-for-of@npm:7.22.15" +"@babel/plugin-transform-for-of@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-for-of@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f395ae7bce31e14961460f56cf751b5d6e37dd27d7df5b1f4e49fec1c11b6f9cf71991c7ffbe6549878591e87df0d66af798cf26edfa4bfa6b4c3dba1fb2f73a + checksum: a53b42dc93ab4b7d1ebd3c695b52be22b3d592f6a3dbdb3dc2fea2c8e0a7e1508fe919864c455cde552aec44ce7518625fccbb70c7063373ca228d884f4f49ea languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-function-name@npm:7.22.5" +"@babel/plugin-transform-function-name@npm:^7.25.1": + version: 7.25.1 + resolution: "@babel/plugin-transform-function-name@npm:7.25.1" dependencies: - "@babel/helper-compilation-targets": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-compilation-targets": ^7.24.8 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/traverse": ^7.25.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cff3b876357999cb8ae30e439c3ec6b0491a53b0aa6f722920a4675a6dd5b53af97a833051df4b34791fe5b3dd326ccf769d5c8e45b322aa50ee11a660b17845 + checksum: 743f3ea03bbc5a90944849d5a880b6bd9243dddbde581a46952da76e53a0b74c1e2424133fe8129d7a152c1f8c872bcd27e0b6728d7caadabd1afa7bb892e1e0 languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-json-strings@npm:7.22.11" +"@babel/plugin-transform-json-strings@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-json-strings@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-json-strings": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 50665e5979e66358c50e90a26db53c55917f78175127ac2fa05c7888d156d418ffb930ec0a109353db0a7c5f57c756ce01bfc9825d24cbfd2b3ec453f2ed8cba + checksum: 88874d0b7a1ddea66c097fc0abb68801ffae194468aa44b828dde9a0e20ac5d8647943793de86092eabaa2911c96f67a6b373793d4bb9c932ef81b2711c06c2e languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-literals@npm:7.22.5" +"@babel/plugin-transform-literals@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/plugin-transform-literals@npm:7.25.2" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ec37cc2ffb32667af935ab32fe28f00920ec8a1eb999aa6dc6602f2bebd8ba205a558aeedcdccdebf334381d5c57106c61f52332045730393e73410892a9735b + checksum: 70c9bb40e377a306bd8f500899fb72127e527517914466e95dc6bb53fa7a0f51479db244a54a771b5780fc1eab488fedd706669bf11097b81a23c81ab7423eb1 languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.22.11" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c664e9798e85afa7f92f07b867682dee7392046181d82f5d21bae6f2ca26dfe9c8375cdc52b7483c3fc09a983c1989f60eff9fbc4f373b0c0a74090553d05739 + checksum: 3367ce0be243704dc6fce23e86a592c4380f01998ee5dd9f94c54b1ef7b971ac6f8a002901eb51599ac6cbdc0d067af8d1a720224fca1c40fde8bb8aab804aac languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.22.5" +"@babel/plugin-transform-member-expression-literals@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ec4b0e07915ddd4fda0142fd104ee61015c208608a84cfa13643a95d18760b1dc1ceb6c6e0548898b8c49e5959a994e46367260176dbabc4467f729b21868504 + checksum: 2720c57aa3bf70576146ba7d6ea03227f4611852122d76d237924f7b008dafc952e6ae61a19e5024f26c665f44384bbd378466f01b6bd1305b3564a3b7fb1a5d languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.22.5": - version: 7.23.0 - resolution: "@babel/plugin-transform-modules-amd@npm:7.23.0" +"@babel/plugin-transform-modules-amd@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7" dependencies: - "@babel/helper-module-transforms": ^7.23.0 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-module-transforms": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 5d92875170a37b8282d4bcd805f55829b8fab0f9c8d08b53d32a7a0bfdc62b868e489b52d329ae768ecafc0c993eed0ad7a387baa673ac33211390a9f833ab5d + checksum: f1dd0fb2f46c0f8f21076b8c7ccd5b33a85ce6dcb31518ea4c648d9a5bb2474cd4bd87c9b1b752e68591e24b022e334ba0d07631fef2b6b4d8a4b85cf3d581f5 languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.22.15, @babel/plugin-transform-modules-commonjs@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.23.0" +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" dependencies: - "@babel/helper-module-transforms": ^7.23.0 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-module-transforms": ^7.24.8 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-simple-access": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7fb25997194053e167c4207c319ff05362392da841bd9f42ddb3caf9c8798a5d203bd926d23ddf5830fdf05eddc82c2810f40d1287e3a4f80b07eff13d1024b5 + checksum: a4cf95b1639c33382064b44558f73ee5fac023f2a94d16e549d2bb55ceebd5cbc10fcddd505d08cd5bc97f5a64af9fd155512358b7dcf7b1a0082e8945cf21c5 languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.22.11": - version: 7.23.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.0" +"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" dependencies: - "@babel/helper-hoist-variables": ^7.22.5 - "@babel/helper-module-transforms": ^7.23.0 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-identifier": ^7.22.20 + "@babel/helper-module-transforms": ^7.25.0 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-validator-identifier": ^7.24.7 + "@babel/traverse": ^7.25.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2d481458b22605046badea2317d5cc5c94ac3031c2293e34c96f02063f5b02af0979c4da6a8fbc67cc249541575dc9c6d710db6b919ede70b7337a22d9fd57a7 + checksum: fe673bec08564e491847324bb80a1e6edfb229f5c37e58a094d51e95306e7b098e1d130fc43e992d22debd93b9beac74441ffc3f6ea5d78f6b2535896efa0728 languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-modules-umd@npm:7.22.5" +"@babel/plugin-transform-modules-umd@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7" dependencies: - "@babel/helper-module-transforms": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-module-transforms": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 46622834c54c551b231963b867adbc80854881b3e516ff29984a8da989bd81665bd70e8cba6710345248e97166689310f544aee1a5773e262845a8f1b3e5b8b4 + checksum: 9ff1c464892efe042952ba778468bda6131b196a2729615bdcc3f24cdc94014f016a4616ee5643c5845bade6ba698f386833e61056d7201314b13a7fd69fac88 languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5" +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: 3ee564ddee620c035b928fdc942c5d17e9c4b98329b76f9cefac65c111135d925eb94ed324064cd7556d4f5123beec79abea1d4b97d1c8a2a5c748887a2eb623 + checksum: f1c6c7b5d60a86b6d7e4dd098798e1d393d55e993a0b57a73b53640c7a94985b601a96bdacee063f809a9a700bcea3a2ff18e98fa561554484ac56b761d774bd languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-new-target@npm:7.22.5" +"@babel/plugin-transform-new-target@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-new-target@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6b72112773487a881a1d6ffa680afde08bad699252020e86122180ee7a88854d5da3f15d9bca3331cf2e025df045604494a8208a2e63b486266b07c14e2ffbf3 + checksum: 3cb94cd1076b270f768f91fdcf9dd2f6d487f8dbfff3df7ca8d07b915900b86d02769a35ba1407d16fe49499012c8f055e1741299e2c880798b953d942a8fa1b languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.22.11" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 167babecc8b8fe70796a7b7d34af667ebbf43da166c21689502e5e8cc93180b7a85979c77c9f64b7cce431b36718bd0a6df9e5e0ffea4ae22afb22cfef886372 + checksum: 4a9221356401d87762afbc37a9e8e764afc2daf09c421117537820f8cfbed6876888372ad3a7bcfae2d45c95f026651f050ab4020b777be31d3ffb00908dbdd3 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.22.11" +"@babel/plugin-transform-numeric-separator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-numeric-separator": ^7.10.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: af064d06a4a041767ec396a5f258103f64785df290e038bba9f0ef454e6c914f2ac45d862bbdad8fac2c7ad47fa4e95356f29053c60c100a0160b02a995fe2a3 + checksum: 561b5f1d08b2c3f92ce849f092751558b5e6cfeb7eb55c79e7375c34dd9c3066dce5e630bb439affef6adcf202b6cbcaaa23870070276fa5bb429c8f5b8c7514 languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.22.15" +"@babel/plugin-transform-object-rest-spread@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7" dependencies: - "@babel/compat-data": ^7.22.9 - "@babel/helper-compilation-targets": ^7.22.15 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-compilation-targets": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-object-rest-spread": ^7.8.3 - "@babel/plugin-transform-parameters": ^7.22.15 + "@babel/plugin-transform-parameters": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 62197a6f12289c1c1bd57f3bed9f0f765ca32390bfe91e0b5561dd94dd9770f4480c4162dec98da094bc0ba99d2c2ebba68de47c019454041b0b7a68ba2ec66d + checksum: 169d257b9800c13e1feb4c37fb05dae84f702e58b342bb76e19e82e6692b7b5337c9923ee89e3916a97c0dd04a3375bdeca14f5e126f110bbacbeb46d1886ca2 languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-object-super@npm:7.22.5" +"@babel/plugin-transform-object-super@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-object-super@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-replace-supers": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b71887877d74cb64dbccb5c0324fa67e31171e6a5311991f626650e44a4083e5436a1eaa89da78c0474fb095d4ec322d63ee778b202d33aa2e4194e1ed8e62d7 + checksum: f71e607a830ee50a22fa1a2686524d3339440cf9dea63032f6efbd865cfe4e35000e1e3f3492459e5c986f7c0c07dc36938bf3ce61fc9ba5f8ab732d0b64ab37 languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.22.11" +"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f17abd90e1de67c84d63afea29c8021c74abb2794d3a6eeafb0bbe7372d3db32aefca386e392116ec63884537a4a2815d090d26264d259bacc08f6e3ed05294c + checksum: 7229f3a5a4facaab40f4fdfc7faabc157dc38a67d66bed7936599f4bc509e0bff636f847ac2aa45294881fce9cf8a0a460b85d2a465b7b977de9739fce9b18f6 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.22.15": - version: 7.23.0 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.23.0" +"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 "@babel/plugin-syntax-optional-chaining": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f702634f2b97e5260dbec0d4bde05ccb6f4d96d7bfa946481aeacfa205ca846cb6e096a38312f9d51fdbdac1f258f211138c5f7075952e46a5bf8574de6a1329 + checksum: 45e55e3a2fffb89002d3f89aef59c141610f23b60eee41e047380bffc40290b59f64fc649aa7ec5281f73d41b2065410d788acc6afaad2a9f44cad6e8af04442 languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-parameters@npm:7.22.15" +"@babel/plugin-transform-parameters@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-parameters@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 541188bb7d1876cad87687b5c7daf90f63d8208ae83df24acb1e2b05020ad1c78786b2723ca4054a83fcb74fb6509f30c4cacc5b538ee684224261ad5fb047c1 + checksum: ab534b03ac2eff94bc79342b8f39a4584666f5305a6c63c1964afda0b1b004e6b861e49d1683548030defe248e3590d3ff6338ee0552cb90c064f7e1479968c3 languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-private-methods@npm:7.22.5" +"@babel/plugin-transform-private-methods@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-class-features-plugin": ^7.25.4 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 321479b4fcb6d3b3ef622ab22fd24001e43d46e680e8e41324c033d5810c84646e470f81b44cbcbef5c22e99030784f7cac92f1829974da7a47a60a7139082c3 + checksum: cb1dabfc03e2977990263d65bc8f43a9037dffbb5d9a5f825c00d05447ff68015099408c1531d9dd88f18a41a90f5062dc48f3a1d52b415d2d2ee4827dedff09 languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.22.11" +"@babel/plugin-transform-private-property-in-object@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-create-class-features-plugin": ^7.22.11 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-annotate-as-pure": ^7.24.7 + "@babel/helper-create-class-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 "@babel/plugin-syntax-private-property-in-object": ^7.14.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4d029d84901e53c46dead7a46e2990a7bc62470f4e4ca58a0d063394f86652fd58fe4eea1eb941da3669cd536b559b9d058b342b59300026346b7a2a51badac8 + checksum: 8cee9473095305cc787bb653fd681719b49363281feabf677db8a552e8e41c94441408055d7e5fd5c7d41b315e634fa70b145ad0c7c54456216049df4ed57350 languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-property-literals@npm:7.22.5" +"@babel/plugin-transform-property-literals@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-property-literals@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 796176a3176106f77fcb8cd04eb34a8475ce82d6d03a88db089531b8f0453a2fb8b0c6ec9a52c27948bc0ea478becec449893741fc546dfc3930ab927e3f9f2e + checksum: 9aeefc3aab6c6bf9d1fae1cf3a2d38c7d886fd3c6c81b7c608c477f5758aee2e7abf52f32724310fe861da61af934ee2508b78a5b5f234b9740c9134e1c14437 languageName: node linkType: hard -"@babel/plugin-transform-react-constant-elements@npm:^7.18.12": - version: 7.20.2 - resolution: "@babel/plugin-transform-react-constant-elements@npm:7.20.2" +"@babel/plugin-transform-react-constant-elements@npm:^7.21.3": + version: 7.25.1 + resolution: "@babel/plugin-transform-react-constant-elements@npm:7.25.1" dependencies: - "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7b041b726e7c14b8c26a0dd240defac5f93a1f449371c6bdc5e6b46d581211300cc1a79da4140bdf20347f49e175dcb4f469812399206864024d1fdc81171193 + checksum: 6126abf8bc3980c1e27fd217f8b2f226b20cce9be300eaf9d30548556dd1e906b7daa4580d9ae1dae35eb5ed5c98e7222e0cb91efb0a232d05aae5875dcfe55c languageName: node linkType: hard @@ -1286,26 +1314,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-regenerator@npm:7.22.10" +"@babel/plugin-transform-regenerator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-regenerator@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 regenerator-transform: ^0.15.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e13678d62d6fa96f11cb8b863f00e8693491e7adc88bfca3f2820f80cbac8336e7dec3a596eee6a1c4663b7ececc3564f2cd7fb44ed6d4ce84ac2bb7f39ecc6e + checksum: 20c6c3fb6fc9f407829087316653388d311e8c1816b007609bb09aeef254092a7157adace8b3aaa8f34be752503717cb85c88a5fe482180a9b11bcbd676063be languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" +"@babel/plugin-transform-reserved-words@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3ffd7dbc425fe8132bfec118b9817572799cab1473113a635d25ab606c1f5a2341a636c04cf6b22df3813320365ed5a965b5eeb3192320a10e4cc2c137bd8bfc + checksum: 3d5876954d5914d7270819479504f30c4bf5452a65c677f44e2dab2db50b3c9d4b47793c45dfad7abf4f377035dd79e4b3f554ae350df9f422201d370ce9f8dd languageName: node linkType: hard @@ -1325,141 +1353,145 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.22.5" +"@babel/plugin-transform-shorthand-properties@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a5ac902c56ea8effa99f681340ee61bac21094588f7aef0bc01dff98246651702e677552fa6d10e548c4ac22a3ffad047dd2f8c8f0540b68316c2c203e56818b + checksum: 7b524245814607188212b8eb86d8c850e5974203328455a30881b4a92c364b93353fae14bc2af5b614ef16300b75b8c1d3b8f3a08355985b4794a7feb240adc3 languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-spread@npm:7.22.5" +"@babel/plugin-transform-spread@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-spread@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 5587f0deb60b3dfc9b274e269031cc45ec75facccf1933ea2ea71ced9fd3ce98ed91bb36d6cd26817c14474b90ed998c5078415f0eab531caf301496ce24c95c + checksum: 4c4254c8b9cceb1a8f975fa9b92257ddb08380a35c0a3721b8f4b9e13a3d82e403af2e0fba577b9f2452dd8f06bc3dea71cc53b1e2c6af595af5db52a13429d6 languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" +"@babel/plugin-transform-sticky-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 63b2c575e3e7f96c32d52ed45ee098fb7d354b35c2223b8c8e76840b32cc529ee0c0ceb5742fd082e56e91e3d82842a367ce177e82b05039af3d602c9627a729 + checksum: 118fc7a7ebf7c20411b670c8a030535fdfe4a88bc5643bb625a584dbc4c8a468da46430a20e6bf78914246962b0f18f1b9d6a62561a7762c4f34a038a5a77179 languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" +"@babel/plugin-transform-template-literals@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-template-literals@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 27e9bb030654cb425381c69754be4abe6a7c75b45cd7f962cd8d604b841b2f0fb7b024f2efc1c25cc53f5b16d79d5e8cfc47cacbdaa983895b3aeefa3e7e24ff + checksum: ad44e5826f5a98c1575832dbdbd033adfe683cdff195e178528ead62507564bf02f479b282976cfd3caebad8b06d5fd7349c1cdb880dec3c56daea4f1f179619 languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.22.5" +"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 82a53a63ffc3010b689ca9a54e5f53b2718b9f4b4a9818f36f9b7dba234f38a01876680553d2716a645a61920b5e6e4aaf8d4a0064add379b27ca0b403049512 + checksum: 8663a8e7347cedf181001d99c88cf794b6598c3d82f324098510fe8fb8bd22113995526a77aa35a3cc5d70ffd0617a59dd0d10311a9bf0e1a3a7d3e59b900c00 languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-typescript@npm:7.22.15" +"@babel/plugin-transform-typescript@npm:^7.24.7": + version: 7.25.2 + resolution: "@babel/plugin-transform-typescript@npm:7.25.2" dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-create-class-features-plugin": ^7.22.15 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/plugin-syntax-typescript": ^7.22.5 + "@babel/helper-annotate-as-pure": ^7.24.7 + "@babel/helper-create-class-features-plugin": ^7.25.0 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 + "@babel/plugin-syntax-typescript": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c5d96cdbf0e1512707aa1c1e3ac6b370a25fd9c545d26008ce44eb13a47bd7fd67a1eb799c98b5ccc82e33a345fda55c0055e1fe3ed97646ed405dd13020b226 + checksum: b0267128d93560a4350919f7230a3b497e20fb8611d9f04bb3560d6b38877305ccad4c40903160263361c6930a84dbcb5b21b8ea923531bda51f67bffdc2dd0b languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.22.10" +"@babel/plugin-transform-unicode-escapes@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 807f40ed1324c8cb107c45358f1903384ca3f0ef1d01c5a3c5c9b271c8d8eec66936a3dcc8d75ddfceea9421420368c2e77ae3adef0a50557e778dfe296bf382 + checksum: 4af0a193e1ddea6ff82b2b15cc2501b872728050bd625740b813c8062fec917d32d530ff6b41de56c15e7296becdf3336a58db81f5ca8e7c445c1306c52f3e01 languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2495e5f663cb388e3d888b4ba3df419ac436a5012144ac170b622ddfc221f9ea9bdba839fa2bc0185cb776b578030666406452ec7791cbf0e7a3d4c88ae9574c + checksum: aae13350c50973f5802ca7906d022a6a0cc0e3aebac9122d0450bbd51e78252d4c2032ad69385e2759fcbdd3aac5d571bd7e26258907f51f8e1a51b53be626c2 languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6b5d1404c8c623b0ec9bd436c00d885a17d6a34f3f2597996343ddb9d94f6379705b21582dfd4cec2c47fd34068872e74ab6b9580116c0566b3f9447e2a7fa06 + checksum: 1cb4e70678906e431da0a05ac3f8350025fee290304ad7482d9cfaa1ca67b2e898654de537c9268efbdad5b80d3ebadf42b4a88ea84609bd8a4cce7b11b48afd languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.25.2 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0 - checksum: c042070f980b139547f8b0179efbc049ac5930abec7fc26ed7a41d89a048d8ab17d362200e204b6f71c3c20d6991a0e74415e1a412a49adc8131c2a40c04822e + checksum: 6d1a7e9fdde4ffc9a81c0e3f261b96a9a0dfe65da282ec96fe63b36c597a7389feac638f1df2a8a4f8c9128337bba8e984f934e9f19077930f33abf1926759ea languageName: node linkType: hard -"@babel/preset-env@npm:^7.19.4, @babel/preset-env@npm:^7.22.9": - version: 7.22.20 - resolution: "@babel/preset-env@npm:7.22.20" +"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.22.9": + version: 7.25.4 + resolution: "@babel/preset-env@npm:7.25.4" dependencies: - "@babel/compat-data": ^7.22.20 - "@babel/helper-compilation-targets": ^7.22.15 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.15 - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.22.15 - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.22.15 + "@babel/compat-data": ^7.25.4 + "@babel/helper-compilation-targets": ^7.25.2 + "@babel/helper-plugin-utils": ^7.24.8 + "@babel/helper-validator-option": ^7.24.8 + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ^7.25.3 + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ^7.25.0 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.25.0 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.24.7 + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ^7.25.0 "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2 "@babel/plugin-syntax-async-generators": ^7.8.4 "@babel/plugin-syntax-class-properties": ^7.12.13 "@babel/plugin-syntax-class-static-block": ^7.14.5 "@babel/plugin-syntax-dynamic-import": ^7.8.3 "@babel/plugin-syntax-export-namespace-from": ^7.8.3 - "@babel/plugin-syntax-import-assertions": ^7.22.5 - "@babel/plugin-syntax-import-attributes": ^7.22.5 + "@babel/plugin-syntax-import-assertions": ^7.24.7 + "@babel/plugin-syntax-import-attributes": ^7.24.7 "@babel/plugin-syntax-import-meta": ^7.10.4 "@babel/plugin-syntax-json-strings": ^7.8.3 "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 @@ -1471,64 +1503,64 @@ __metadata: "@babel/plugin-syntax-private-property-in-object": ^7.14.5 "@babel/plugin-syntax-top-level-await": ^7.14.5 "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6 - "@babel/plugin-transform-arrow-functions": ^7.22.5 - "@babel/plugin-transform-async-generator-functions": ^7.22.15 - "@babel/plugin-transform-async-to-generator": ^7.22.5 - "@babel/plugin-transform-block-scoped-functions": ^7.22.5 - "@babel/plugin-transform-block-scoping": ^7.22.15 - "@babel/plugin-transform-class-properties": ^7.22.5 - "@babel/plugin-transform-class-static-block": ^7.22.11 - "@babel/plugin-transform-classes": ^7.22.15 - "@babel/plugin-transform-computed-properties": ^7.22.5 - "@babel/plugin-transform-destructuring": ^7.22.15 - "@babel/plugin-transform-dotall-regex": ^7.22.5 - "@babel/plugin-transform-duplicate-keys": ^7.22.5 - "@babel/plugin-transform-dynamic-import": ^7.22.11 - "@babel/plugin-transform-exponentiation-operator": ^7.22.5 - "@babel/plugin-transform-export-namespace-from": ^7.22.11 - "@babel/plugin-transform-for-of": ^7.22.15 - "@babel/plugin-transform-function-name": ^7.22.5 - "@babel/plugin-transform-json-strings": ^7.22.11 - "@babel/plugin-transform-literals": ^7.22.5 - "@babel/plugin-transform-logical-assignment-operators": ^7.22.11 - "@babel/plugin-transform-member-expression-literals": ^7.22.5 - "@babel/plugin-transform-modules-amd": ^7.22.5 - "@babel/plugin-transform-modules-commonjs": ^7.22.15 - "@babel/plugin-transform-modules-systemjs": ^7.22.11 - "@babel/plugin-transform-modules-umd": ^7.22.5 - "@babel/plugin-transform-named-capturing-groups-regex": ^7.22.5 - "@babel/plugin-transform-new-target": ^7.22.5 - "@babel/plugin-transform-nullish-coalescing-operator": ^7.22.11 - "@babel/plugin-transform-numeric-separator": ^7.22.11 - "@babel/plugin-transform-object-rest-spread": ^7.22.15 - "@babel/plugin-transform-object-super": ^7.22.5 - "@babel/plugin-transform-optional-catch-binding": ^7.22.11 - "@babel/plugin-transform-optional-chaining": ^7.22.15 - "@babel/plugin-transform-parameters": ^7.22.15 - "@babel/plugin-transform-private-methods": ^7.22.5 - "@babel/plugin-transform-private-property-in-object": ^7.22.11 - "@babel/plugin-transform-property-literals": ^7.22.5 - "@babel/plugin-transform-regenerator": ^7.22.10 - "@babel/plugin-transform-reserved-words": ^7.22.5 - "@babel/plugin-transform-shorthand-properties": ^7.22.5 - "@babel/plugin-transform-spread": ^7.22.5 - "@babel/plugin-transform-sticky-regex": ^7.22.5 - "@babel/plugin-transform-template-literals": ^7.22.5 - "@babel/plugin-transform-typeof-symbol": ^7.22.5 - "@babel/plugin-transform-unicode-escapes": ^7.22.10 - "@babel/plugin-transform-unicode-property-regex": ^7.22.5 - "@babel/plugin-transform-unicode-regex": ^7.22.5 - "@babel/plugin-transform-unicode-sets-regex": ^7.22.5 + "@babel/plugin-transform-arrow-functions": ^7.24.7 + "@babel/plugin-transform-async-generator-functions": ^7.25.4 + "@babel/plugin-transform-async-to-generator": ^7.24.7 + "@babel/plugin-transform-block-scoped-functions": ^7.24.7 + "@babel/plugin-transform-block-scoping": ^7.25.0 + "@babel/plugin-transform-class-properties": ^7.25.4 + "@babel/plugin-transform-class-static-block": ^7.24.7 + "@babel/plugin-transform-classes": ^7.25.4 + "@babel/plugin-transform-computed-properties": ^7.24.7 + "@babel/plugin-transform-destructuring": ^7.24.8 + "@babel/plugin-transform-dotall-regex": ^7.24.7 + "@babel/plugin-transform-duplicate-keys": ^7.24.7 + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ^7.25.0 + "@babel/plugin-transform-dynamic-import": ^7.24.7 + "@babel/plugin-transform-exponentiation-operator": ^7.24.7 + "@babel/plugin-transform-export-namespace-from": ^7.24.7 + "@babel/plugin-transform-for-of": ^7.24.7 + "@babel/plugin-transform-function-name": ^7.25.1 + "@babel/plugin-transform-json-strings": ^7.24.7 + "@babel/plugin-transform-literals": ^7.25.2 + "@babel/plugin-transform-logical-assignment-operators": ^7.24.7 + "@babel/plugin-transform-member-expression-literals": ^7.24.7 + "@babel/plugin-transform-modules-amd": ^7.24.7 + "@babel/plugin-transform-modules-commonjs": ^7.24.8 + "@babel/plugin-transform-modules-systemjs": ^7.25.0 + "@babel/plugin-transform-modules-umd": ^7.24.7 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.24.7 + "@babel/plugin-transform-new-target": ^7.24.7 + "@babel/plugin-transform-nullish-coalescing-operator": ^7.24.7 + "@babel/plugin-transform-numeric-separator": ^7.24.7 + "@babel/plugin-transform-object-rest-spread": ^7.24.7 + "@babel/plugin-transform-object-super": ^7.24.7 + "@babel/plugin-transform-optional-catch-binding": ^7.24.7 + "@babel/plugin-transform-optional-chaining": ^7.24.8 + "@babel/plugin-transform-parameters": ^7.24.7 + "@babel/plugin-transform-private-methods": ^7.25.4 + "@babel/plugin-transform-private-property-in-object": ^7.24.7 + "@babel/plugin-transform-property-literals": ^7.24.7 + "@babel/plugin-transform-regenerator": ^7.24.7 + "@babel/plugin-transform-reserved-words": ^7.24.7 + "@babel/plugin-transform-shorthand-properties": ^7.24.7 + "@babel/plugin-transform-spread": ^7.24.7 + "@babel/plugin-transform-sticky-regex": ^7.24.7 + "@babel/plugin-transform-template-literals": ^7.24.7 + "@babel/plugin-transform-typeof-symbol": ^7.24.8 + "@babel/plugin-transform-unicode-escapes": ^7.24.7 + "@babel/plugin-transform-unicode-property-regex": ^7.24.7 + "@babel/plugin-transform-unicode-regex": ^7.24.7 + "@babel/plugin-transform-unicode-sets-regex": ^7.25.4 "@babel/preset-modules": 0.1.6-no-external-plugins - "@babel/types": ^7.22.19 - babel-plugin-polyfill-corejs2: ^0.4.5 - babel-plugin-polyfill-corejs3: ^0.8.3 - babel-plugin-polyfill-regenerator: ^0.5.2 - core-js-compat: ^3.31.0 + babel-plugin-polyfill-corejs2: ^0.4.10 + babel-plugin-polyfill-corejs3: ^0.10.6 + babel-plugin-polyfill-regenerator: ^0.6.1 + core-js-compat: ^3.37.1 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 99357a5cb30f53bacdc0d1cd6dff0f052ea6c2d1ba874d969bba69897ef716e87283e84a59dc52fb49aa31fd1b6f55ed756c64c04f5678380700239f6030b881 + checksum: 752be43f0b78a2eefe5007076aed3d21b505e1c09d134b61e7de8838f1bbb1e7af81023d39adb14b6eae23727fb5a9fd23f8115a44df043319be22319be17913 languageName: node linkType: hard @@ -1561,18 +1593,18 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.18.6, @babel/preset-typescript@npm:^7.22.5": - version: 7.23.0 - resolution: "@babel/preset-typescript@npm:7.23.0" +"@babel/preset-typescript@npm:^7.21.0, @babel/preset-typescript@npm:^7.22.5": + version: 7.24.7 + resolution: "@babel/preset-typescript@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.15 - "@babel/plugin-syntax-jsx": ^7.22.5 - "@babel/plugin-transform-modules-commonjs": ^7.23.0 - "@babel/plugin-transform-typescript": ^7.22.15 + "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-validator-option": ^7.24.7 + "@babel/plugin-syntax-jsx": ^7.24.7 + "@babel/plugin-transform-modules-commonjs": ^7.24.7 + "@babel/plugin-transform-typescript": ^7.24.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3d5fce83e83f11c07e0ea13542bca181abb3b482b8981ec9c64e6add9d7beed3c54d063dc4bc9fd383165c71114a245abef89a289680833c5a8552fe3e7c4407 + checksum: 12929b24757f3bd6548103475f86478eda4c872bc7cefd920b29591eee8f4a4f350561d888e133d632d0c9402b8615fdcec9138e5127a6567dcb22f804ff207f languageName: node linkType: hard @@ -1602,43 +1634,40 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.22.15, @babel/template@npm:^7.22.5, @babel/template@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/template@npm:7.23.9" +"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" dependencies: - "@babel/code-frame": ^7.23.5 - "@babel/parser": ^7.23.9 - "@babel/types": ^7.23.9 - checksum: 6e67414c0f7125d7ecaf20c11fab88085fa98a96c3ef10da0a61e962e04fdf3a18a496a66047005ddd1bb682a7cc7842d556d1db2f3f3f6ccfca97d5e445d342 + "@babel/code-frame": ^7.24.7 + "@babel/parser": ^7.25.0 + "@babel/types": ^7.25.0 + checksum: 3f2db568718756d0daf2a16927b78f00c425046b654cd30b450006f2e84bdccaf0cbe6dc04994aa1f5f6a4398da2f11f3640a4d3ee31722e43539c4c919c817b languageName: node linkType: hard -"@babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/traverse@npm:7.23.9" +"@babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/traverse@npm:7.25.4" dependencies: - "@babel/code-frame": ^7.23.5 - "@babel/generator": ^7.23.6 - "@babel/helper-environment-visitor": ^7.22.20 - "@babel/helper-function-name": ^7.23.0 - "@babel/helper-hoist-variables": ^7.22.5 - "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/parser": ^7.23.9 - "@babel/types": ^7.23.9 + "@babel/code-frame": ^7.24.7 + "@babel/generator": ^7.25.4 + "@babel/parser": ^7.25.4 + "@babel/template": ^7.25.0 + "@babel/types": ^7.25.4 debug: ^4.3.1 globals: ^11.1.0 - checksum: a932f7aa850e158c00c97aad22f639d48c72805c687290f6a73e30c5c4957c07f5d28310c9bf59648e2980fe6c9d16adeb2ff92a9ca0f97fa75739c1328fc6c3 + checksum: 3b6d879b9d843b119501585269b3599f047011ae21eb7820d00aef62fc3a2bcdaf6f4cdf2679795a2d7c0b6b5d218974916e422f08dea08613dc42188ef21e4b languageName: node linkType: hard -"@babel/types@npm:^7.20.0, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.23.9 - resolution: "@babel/types@npm:7.23.9" +"@babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.4.4": + version: 7.25.4 + resolution: "@babel/types@npm:7.25.4" dependencies: - "@babel/helper-string-parser": ^7.23.4 - "@babel/helper-validator-identifier": ^7.22.20 + "@babel/helper-string-parser": ^7.24.8 + "@babel/helper-validator-identifier": ^7.24.7 to-fast-properties: ^2.0.0 - checksum: 0a9b008e9bfc89beb8c185e620fa0f8ed6c771f1e1b2e01e1596870969096fec7793898a1d64a035176abf1dd13e2668ee30bf699f2d92c210a8128f4b151e65 + checksum: 497f8b583c54a92a59c3ec542144695064cd5c384fcca46ba1aa301d5e5dd6c1d011f312ca024cb0f9c956da07ae82fb4c348c31a30afa31a074c027720d2aa8 languageName: node linkType: hard @@ -1689,9 +1718,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/core@npm:3.2.1, @docusaurus/core@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/core@npm:3.2.1" +"@docusaurus/core@npm:3.5.2, @docusaurus/core@npm:^3.1.1": + version: 3.5.2 + resolution: "@docusaurus/core@npm:3.5.2" dependencies: "@babel/core": ^7.23.3 "@babel/generator": ^7.23.3 @@ -1703,14 +1732,12 @@ __metadata: "@babel/runtime": ^7.22.6 "@babel/runtime-corejs3": ^7.22.6 "@babel/traverse": ^7.22.8 - "@docusaurus/cssnano-preset": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 - "@svgr/webpack": ^6.5.1 + "@docusaurus/cssnano-preset": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 autoprefixer: ^10.4.14 babel-loader: ^9.1.3 babel-plugin-dynamic-import-node: ^2.3.3 @@ -1724,8 +1751,8 @@ __metadata: copy-webpack-plugin: ^11.0.0 core-js: ^3.31.1 css-loader: ^6.8.1 - css-minimizer-webpack-plugin: ^4.2.2 - cssnano: ^5.1.15 + css-minimizer-webpack-plugin: ^5.0.1 + cssnano: ^6.1.2 del: ^6.1.1 detect-port: ^1.5.1 escape-html: ^1.0.3 @@ -1745,7 +1772,7 @@ __metadata: prompts: ^2.4.2 react-dev-utils: ^12.0.1 react-helmet-async: ^1.3.0 - react-loadable: "npm:@docusaurus/react-loadable@5.5.2" + react-loadable: "npm:@docusaurus/react-loadable@6.0.0" react-loadable-ssr-addon-v5-slorber: ^1.0.1 react-router: ^5.3.4 react-router-config: ^5.1.1 @@ -1764,43 +1791,44 @@ __metadata: webpack-merge: ^5.9.0 webpackbar: ^5.0.2 peerDependencies: + "@mdx-js/react": ^3.0.0 react: ^18.0.0 react-dom: ^18.0.0 bin: docusaurus: bin/docusaurus.mjs - checksum: 9267f08b41240cb9d399abbd8a41ff66e0082551284325db3f17fcce9643bef81d06564797a7cc4c528fe8bde2858c20666e74a0308f3ecc80f3be1dbee14bb5 + checksum: 6c6282a75931f0f8f8f8768232b4436ff8679ae12b619f7bd01e0d83aa346e24ab0d9cecac034f9dc95c55059997efdd963d052d3e429583bfb8d3b54ab750d3 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/cssnano-preset@npm:3.2.1" +"@docusaurus/cssnano-preset@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/cssnano-preset@npm:3.5.2" dependencies: - cssnano-preset-advanced: ^5.3.10 - postcss: ^8.4.26 - postcss-sort-media-queries: ^4.4.1 + cssnano-preset-advanced: ^6.1.2 + postcss: ^8.4.38 + postcss-sort-media-queries: ^5.2.0 tslib: ^2.6.0 - checksum: ee23a1229d23732d936fe1d68732d1305abf0132b43a398336fee500504a3e7566d3b0c6222f89f565e24e68e00e353765e0cbbab5611a3b35ecf88305558b6d + checksum: 4bb1fae3741e14cbbdb64c1b0707435970838bf219831234a70cf382e6811ffac1cadf733d5e1fe7c278e7b2a9e533bfa802a5212b22ec46edd703208cf49f92 languageName: node linkType: hard -"@docusaurus/logger@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/logger@npm:3.2.1" +"@docusaurus/logger@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/logger@npm:3.5.2" dependencies: chalk: ^4.1.2 tslib: ^2.6.0 - checksum: 9d5db5253eda98871563faddb5318bcb6b17ddf5882ababad4803d526917844819751e84ee8028e794fd5507646db6409f9041fd7f41b7f7971015df11cc6376 + checksum: 7cbdcf54acd6e7787ca5a10b9c884be4b9e8fdae837862c66550a0bf3d02737f72c3188b2bddd61da6d8530eb2eb2b646ea599a79416e33c4998f1a87d2f6a8c languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/mdx-loader@npm:3.2.1" +"@docusaurus/mdx-loader@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/mdx-loader@npm:3.5.2" dependencies: - "@docusaurus/logger": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/logger": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 "@mdx-js/mdx": ^3.0.0 "@slorber/remark-comment": ^1.0.0 escape-html: ^1.0.3 @@ -1825,38 +1853,37 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 4609faf2d8b76085a3aa86ac5ca4ac3b3420e3cfd796f1b39c46f368c82b3db0db5b1308646cf35fdad0a1f6f088d367116eb0e2a8c3fa728ed886ee37516476 + checksum: 36186c2f3487631757b24ba3a21575d2253ca1e6ada82d556bf323da7ae7637c0880eb388bf375e207bc5f26dcd8b58cc76d763e6c2caf6ed80f88748444ce8d languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:3.2.1, @docusaurus/module-type-aliases@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/module-type-aliases@npm:3.2.1" +"@docusaurus/module-type-aliases@npm:3.5.2, @docusaurus/module-type-aliases@npm:^3.1.1": + version: 3.5.2 + resolution: "@docusaurus/module-type-aliases@npm:3.5.2" dependencies: - "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/types": 3.2.1 + "@docusaurus/types": 3.5.2 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" "@types/react-router-dom": "*" react-helmet-async: "*" - react-loadable: "npm:@docusaurus/react-loadable@5.5.2" + react-loadable: "npm:@docusaurus/react-loadable@6.0.0" peerDependencies: react: "*" react-dom: "*" - checksum: 37b4a40f9afebbe76e350c10c857737b544c141a988462436904ae16993a52e4429018d406e2f55ad57a533e5a108dd7cdb903434abb84721deeec0d5f195d80 + checksum: 0161db859d459bb25ac162f0c509fb1316dfb403a9e89f325a9bc7d9f35ae1825b9703a435777903ba93de827d4413b189bbd0c03018ac13d66b50633302ea80 languageName: node linkType: hard "@docusaurus/plugin-client-redirects@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-client-redirects@npm:3.2.1" + version: 3.5.2 + resolution: "@docusaurus/plugin-client-redirects@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 eta: ^2.2.0 fs-extra: ^11.1.1 lodash: ^4.17.21 @@ -1864,22 +1891,23 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 84f031a9f660028cf745ad4f3b8ea8820e34fdabed75321b06a8145078b3b8c496369c902dc3f46a2bb973cfaaa433474a9891bd836494ff72c65258fa67b64c + checksum: 040aa829e819a43cb8d30f6c776c27a303080a4cb2fc2c398266fe9ecbd006dc53c6dd862ebcf4f74d0547042c4df6c4bbf080b3702be6df18c95b95c3e38f1d languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-content-blog@npm:3.2.1" +"@docusaurus/plugin-content-blog@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-content-blog@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 - cheerio: ^1.0.0-rc.12 + "@docusaurus/core": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/theme-common": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 + cheerio: 1.0.0-rc.12 feed: ^4.2.2 fs-extra: ^11.1.1 lodash: ^4.17.21 @@ -1890,24 +1918,26 @@ __metadata: utility-types: ^3.10.0 webpack: ^5.88.1 peerDependencies: + "@docusaurus/plugin-content-docs": "*" react: ^18.0.0 react-dom: ^18.0.0 - checksum: d95147a28aad832cd2dc39af634e1902a8a36f958dd2ff5fa6eaa47b574b58df42609a64da823951826f647337ad35c1f1c8be8a0a085913e192936f38839413 + checksum: c5997b9d86ccf939998f9d56e65491ecf9e677d8425e95a79b3b428041d4dfc4ecb03a18ef595777c3ad5bd65f4a2dd30d99cb6f1b411161bf7cd32027ecc6d5 languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-content-docs@npm:3.2.1" +"@docusaurus/plugin-content-docs@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-content-docs@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/module-type-aliases": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/module-type-aliases": 3.5.2 + "@docusaurus/theme-common": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 "@types/react-router-config": ^5.0.7 combine-promises: ^1.1.0 fs-extra: ^11.1.1 @@ -1919,168 +1949,156 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: c182466c3ff513b36a8975a3899b07ffc4b227ab45ef69eacc0a77119d6f0cd6a0727a3e886cfcf4a56e4f522f64e1e6a2647ddc57eb8493b93c03240b1d9b39 + checksum: fb7ba7f8a6741b14bbe8db0bf1b12ff7a24d12c40d8276f32b9b393881d74bfed3bed4f1e5b0756cac0e43c4bd8106094d5cf6a3c527400e9713283fc3832dab languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-content-pages@npm:3.2.1" +"@docusaurus/plugin-content-pages@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-content-pages@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 fs-extra: ^11.1.1 tslib: ^2.6.0 webpack: ^5.88.1 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 3cce99f8aa863b97cbb54a50b448073222a0678528b09f5bec2196e73ec4740f412f8675ed05d283ff672756a5d3005f7a1e4d8c8f882cd0d6d5691cbccb604c + checksum: 8b3f1040e8ec006c9431508e73ef3f61cd5759bece3770189f7d52609f91bd156c9b18d0608f9cb14c456a1d1823be6633c573d5eee7cf9bd142b0f978c7a745 languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-debug@npm:3.2.1" +"@docusaurus/plugin-debug@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-debug@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 fs-extra: ^11.1.1 react-json-view-lite: ^1.2.0 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: b3fb1c8935463afb97f233042692c247d4147c03e18ef9fb37fbf0c46d4adaefa4af0d5c357025992dadfe7b83a9fd3754946b8947bfb8b9535dca390a3668d0 + checksum: a839e6c3a595ea202fdd7fbce638ab8df26ba73a8c7ead8c04d1bbb509ebe34e9633e7fe9eb54a7a733e93a03d74a60df4d9f6597b9621ff464280d4dd71db34 languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-google-analytics@npm:3.2.1" +"@docusaurus/plugin-google-analytics@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-google-analytics@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: e1e881fd6adbe408029257d526759b9217f7d70e5e068c7e9241a5f0c3050b0fa46acfeb4f8a23c3f36e1739d0a3d810642d69c6648ff6801ce13b646e44e6c1 + checksum: 0b8c4d21333d40c2509d6ef807caaf69f085010c5deac514ab34f53b5486fd76766c90213dc98976a6c4d66fdfa14bf6b05594e51e8a53ec60c2a3fa08fd9a83 languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-google-gtag@npm:3.2.1" +"@docusaurus/plugin-google-gtag@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-google-gtag@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 "@types/gtag.js": ^0.0.12 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: b7758289d8453e98baf95d41e754c1e4c8fd5b1c000ba444c4bdf13fc97674a3cddf3215b6406266729e23898641b5bae297c5422c5bd079ef04773fa5a15c1b + checksum: 5d53c2483c8c7e3a8e842bd091a774d4041f0e165d216b3c02f031a224a77258c9456e8b2acd0500b4a0eff474a83c1b82803628db9d4b132514409936c68ac4 languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.2.1" +"@docusaurus/plugin-google-tag-manager@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-google-tag-manager@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 82355aed046b12ce0fead68339e24a3c6f2f517bc2b80c9c26c502cc49d86c1b6d0f797d5269d1d5e73ac78fd748c8a2f4528f7f3feee1137ae8e73876426426 + checksum: 9a6fc2ca54ea677c6edfd78f4f392d7d9ae86afd085fcda96d5ac41efa441352c25a2519595d9d15fb9b838e2ae39837f0daf02e2406c5cd56199ae237bd7b7a languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/plugin-sitemap@npm:3.2.1" +"@docusaurus/plugin-sitemap@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/plugin-sitemap@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 fs-extra: ^11.1.1 sitemap: ^7.1.1 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: b2e4c4fddd0fbdd4a6a4c93a0f9c16b1294162146eb9911ce378f33d70396f08dfa98d92aed133bba2a8df2b1710c257bf00c0657933ee6cd9c5edb36c8054dc + checksum: 26b6bceb7ab87fe7f6f666742d1e81de32cdacc5aaa3d45d91002c7d64e3258f3d0aac87c6b0d442eaf34ede2af4b7521b50737f2e8e2718daff6fce10230213 languageName: node linkType: hard "@docusaurus/preset-classic@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/preset-classic@npm:3.2.1" + version: 3.5.2 + resolution: "@docusaurus/preset-classic@npm:3.5.2" dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/plugin-content-blog": 3.2.1 - "@docusaurus/plugin-content-docs": 3.2.1 - "@docusaurus/plugin-content-pages": 3.2.1 - "@docusaurus/plugin-debug": 3.2.1 - "@docusaurus/plugin-google-analytics": 3.2.1 - "@docusaurus/plugin-google-gtag": 3.2.1 - "@docusaurus/plugin-google-tag-manager": 3.2.1 - "@docusaurus/plugin-sitemap": 3.2.1 - "@docusaurus/theme-classic": 3.2.1 - "@docusaurus/theme-common": 3.2.1 - "@docusaurus/theme-search-algolia": 3.2.1 - "@docusaurus/types": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/plugin-content-blog": 3.5.2 + "@docusaurus/plugin-content-docs": 3.5.2 + "@docusaurus/plugin-content-pages": 3.5.2 + "@docusaurus/plugin-debug": 3.5.2 + "@docusaurus/plugin-google-analytics": 3.5.2 + "@docusaurus/plugin-google-gtag": 3.5.2 + "@docusaurus/plugin-google-tag-manager": 3.5.2 + "@docusaurus/plugin-sitemap": 3.5.2 + "@docusaurus/theme-classic": 3.5.2 + "@docusaurus/theme-common": 3.5.2 + "@docusaurus/theme-search-algolia": 3.5.2 + "@docusaurus/types": 3.5.2 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 343c896f22bffbda9db4af7d652588f353c5f60336e545eb07be0dfe9bc29ca04a3978d88d5a8b3fa7caafc56a48b341349ffd08006885fa0d4de216cfdc5401 + checksum: ec578e62b3b13b1874b14235a448a913c2d2358ea9b9d9c60bb250be468ab62387c88ec44e1ee82ad5b3d7243306e31919888a80eae62e5e8eab0ae12194bf69 languageName: node linkType: hard -"@docusaurus/react-loadable@npm:5.5.2, react-loadable@npm:@docusaurus/react-loadable@5.5.2": - version: 5.5.2 - resolution: "@docusaurus/react-loadable@npm:5.5.2" +"@docusaurus/theme-classic@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/theme-classic@npm:3.5.2" dependencies: - "@types/react": "*" - prop-types: ^15.6.2 - peerDependencies: - react: "*" - checksum: 930fb9e2936412a12461f210acdc154a433283921ca43ac3fc3b84cb6c12eb738b3a3719373022bf68004efeb1a928dbe36c467d7a1f86454ed6241576d936e7 - languageName: node - linkType: hard - -"@docusaurus/theme-classic@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/theme-classic@npm:3.2.1" - dependencies: - "@docusaurus/core": 3.2.1 - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/module-type-aliases": 3.2.1 - "@docusaurus/plugin-content-blog": 3.2.1 - "@docusaurus/plugin-content-docs": 3.2.1 - "@docusaurus/plugin-content-pages": 3.2.1 - "@docusaurus/theme-common": 3.2.1 - "@docusaurus/theme-translations": 3.2.1 - "@docusaurus/types": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/module-type-aliases": 3.5.2 + "@docusaurus/plugin-content-blog": 3.5.2 + "@docusaurus/plugin-content-docs": 3.5.2 + "@docusaurus/plugin-content-pages": 3.5.2 + "@docusaurus/theme-common": 3.5.2 + "@docusaurus/theme-translations": 3.5.2 + "@docusaurus/types": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 "@mdx-js/react": ^3.0.0 clsx: ^2.0.0 copy-text-to-clipboard: ^3.2.0 - infima: 0.2.0-alpha.43 + infima: 0.2.0-alpha.44 lodash: ^4.17.21 nprogress: ^0.2.0 postcss: ^8.4.26 @@ -2093,21 +2111,18 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 7b38e47e9334ba6ad84f6432ec9ae81caad7f6c630b2a332617b0f32f1559b0e56f3d8857c732da62d1d7213ad0f493853bf18b1707a2f8d8bcccef32f1d81a1 + checksum: 6c415b01ad24bb43eb166e2b780a84356ff14a627627f6a541c2803832d56c4f9409a5636048693d2d24804f59c2cc7bda925d9ef999a8276fe125477d2b2e1e languageName: node linkType: hard -"@docusaurus/theme-common@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/theme-common@npm:3.2.1" +"@docusaurus/theme-common@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/theme-common@npm:3.5.2" dependencies: - "@docusaurus/mdx-loader": 3.2.1 - "@docusaurus/module-type-aliases": 3.2.1 - "@docusaurus/plugin-content-blog": 3.2.1 - "@docusaurus/plugin-content-docs": 3.2.1 - "@docusaurus/plugin-content-pages": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 + "@docusaurus/mdx-loader": 3.5.2 + "@docusaurus/module-type-aliases": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -2117,24 +2132,25 @@ __metadata: tslib: ^2.6.0 utility-types: ^3.10.0 peerDependencies: + "@docusaurus/plugin-content-docs": "*" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 13de70293476e05f1b52c2d99a1b26c73bf99ac92aba3c8ddc413b5336725d2b54c56c167d12244fdb0b518ee9cdecbbfb3258fb8cc91272e9b795361b131fbb + checksum: c78ec7f6035abc920a2a0bc1ad78920178a5452538a3a70794eca8d4b976725f6ccc464ee3092afd31ca59b4e061ad4c21cdce7f5e10b06567075814b2fc2002 languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/theme-search-algolia@npm:3.2.1" +"@docusaurus/theme-search-algolia@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/theme-search-algolia@npm:3.5.2" dependencies: "@docsearch/react": ^3.5.2 - "@docusaurus/core": 3.2.1 - "@docusaurus/logger": 3.2.1 - "@docusaurus/plugin-content-docs": 3.2.1 - "@docusaurus/theme-common": 3.2.1 - "@docusaurus/theme-translations": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-validation": 3.2.1 + "@docusaurus/core": 3.5.2 + "@docusaurus/logger": 3.5.2 + "@docusaurus/plugin-content-docs": 3.5.2 + "@docusaurus/theme-common": 3.5.2 + "@docusaurus/theme-translations": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-validation": 3.5.2 algoliasearch: ^4.18.0 algoliasearch-helper: ^3.13.3 clsx: ^2.0.0 @@ -2146,30 +2162,30 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: befbb86bf309f2b770ae21bc1d5c91eb6e840a5a72858cdfd3b21dbabadd1738d6d427ada7745f9d3424bb1a6e01839e20bf35c15a4c13d59b63d259e52de5df + checksum: e945e3001996477597bfad074eaef074cf4c5365ed3076c3109130a2252b266e4e2fac46904a0626eedeff23b9ac11e7b985cc71f5485ede52d3ddf379b7959b languageName: node linkType: hard -"@docusaurus/theme-translations@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/theme-translations@npm:3.2.1" +"@docusaurus/theme-translations@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/theme-translations@npm:3.5.2" dependencies: fs-extra: ^11.1.1 tslib: ^2.6.0 - checksum: 43bdb90d143576d2e8eb56bfe2c9daa0e4250cdb2dcfd10096b86466e6ee253548ac5ef2f9a4986a5bc9a573d118fe4695ee5004f0ef00b57b720dac7f124337 + checksum: dc523c74a13fb8552c03e547c6de1c21881d899cc74bf088a2bed716e0ef1a4ceba2726c43656d87fff60413ca191f5ea946b182e4ae4129c14da832b5194d82 languageName: node linkType: hard "@docusaurus/tsconfig@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/tsconfig@npm:3.2.1" - checksum: ea3c28b79b0de069c50f7b3a67d3ff682b6ded2ef02d2c7a4c2eaeddc8fcf79c9d9f5e60fbd2966cf3d247fbb8f63897b80a61fdd8b485c745a12eb684ae241a + version: 3.5.2 + resolution: "@docusaurus/tsconfig@npm:3.5.2" + checksum: 808a17eaf422ae9a948c6558dd1e92d4700b067ead3a63a84049c6845bf94f84e311cd0e4d517047fe9ea057efe393bb22c2d5c92d727d06c9f895e971f2c3ea languageName: node linkType: hard -"@docusaurus/types@npm:3.2.1, @docusaurus/types@npm:^3.1.1": - version: 3.2.1 - resolution: "@docusaurus/types@npm:3.2.1" +"@docusaurus/types@npm:3.5.2, @docusaurus/types@npm:^3.1.1": + version: 3.5.2 + resolution: "@docusaurus/types@npm:3.5.2" dependencies: "@mdx-js/mdx": ^3.0.0 "@types/history": ^4.7.11 @@ -2183,13 +2199,13 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 4f19e162bff627675df160ae5c33c6063646050c4de5c9698018fbd9d198300b9ce7a7333e4d1b369b42cfa42296dc9fb36547e4e37664d594deb08639e6b620 + checksum: e39451b7b08673ad5e1551ee6e4286f90f2554cf9ba245abfa56670550f48afca9c57b01c10ffa21dacb734c0fcd067150eeb2b1c1ebb1692f1f538b1eed0029 languageName: node linkType: hard -"@docusaurus/utils-common@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/utils-common@npm:3.2.1" +"@docusaurus/utils-common@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/utils-common@npm:3.5.2" dependencies: tslib: ^2.6.0 peerDependencies: @@ -2197,31 +2213,33 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: bc0b7e74bc29134dbdb7fbc2e8f9f39f0f460923a07d0ccd7f0542088e92c47faf06bdbd253b7ba2b9250b0869118a3b7bf3faa3a075a2a35f5f8545eb3345f2 + checksum: 9d550c89663d4271456ae0832c82a1691207ccc95e21df3a05a4bd6bbd2624bb9e3ab7327d939c04b2023378987bcf99321b2c37be1af214852832f65d6db14a languageName: node linkType: hard -"@docusaurus/utils-validation@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/utils-validation@npm:3.2.1" +"@docusaurus/utils-validation@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/utils-validation@npm:3.5.2" dependencies: - "@docusaurus/logger": 3.2.1 - "@docusaurus/utils": 3.2.1 - "@docusaurus/utils-common": 3.2.1 + "@docusaurus/logger": 3.5.2 + "@docusaurus/utils": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + fs-extra: ^11.2.0 joi: ^17.9.2 js-yaml: ^4.1.0 + lodash: ^4.17.21 tslib: ^2.6.0 - checksum: c7b5142083c8e4798c7f6aa1f7a06bc2e93e8e08a8a7a2c5eaf24aa6939e12e401f180f02164764805c40ec0f7179479e0ee98a935c2cb77037ca73ab33d80fd + checksum: 5966e6d0e8f26292c629899f13b545501b53b345b0e2291bb47aaa80d7c9c5cf155e15a4ecd073a4095ee7c83c6db3612e0a34f81a8187fd20410b1aeb92d731 languageName: node linkType: hard -"@docusaurus/utils@npm:3.2.1": - version: 3.2.1 - resolution: "@docusaurus/utils@npm:3.2.1" +"@docusaurus/utils@npm:3.5.2": + version: 3.5.2 + resolution: "@docusaurus/utils@npm:3.5.2" dependencies: - "@docusaurus/logger": 3.2.1 - "@docusaurus/utils-common": 3.2.1 - "@svgr/webpack": ^6.5.1 + "@docusaurus/logger": 3.5.2 + "@docusaurus/utils-common": 3.5.2 + "@svgr/webpack": ^8.1.0 escape-string-regexp: ^4.0.0 file-loader: ^6.2.0 fs-extra: ^11.1.1 @@ -2237,13 +2255,14 @@ __metadata: shelljs: ^0.8.5 tslib: ^2.6.0 url-loader: ^4.1.1 + utility-types: ^3.10.0 webpack: ^5.88.1 peerDependencies: "@docusaurus/types": "*" peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: ea862b178e303b49e644e77a663df6e42909632022918b77dc1ee69c4de46dde3f210052b1063e96a820e1443141f70e44aa51372f2bf9cfde65e080ea639889 + checksum: 0e0f4fc65ed076d4e4b551ecb61447b7c2468060d1655afff314515844ae34dc0546f467f53bff535f3144afc109e974da27fadb7c678a5d19966bed9e7a27c4 languageName: node linkType: hard @@ -2270,51 +2289,51 @@ __metadata: languageName: node linkType: hard -"@jest/schemas@npm:^29.0.0": - version: 29.0.0 - resolution: "@jest/schemas@npm:29.0.0" +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" dependencies: - "@sinclair/typebox": ^0.24.1 - checksum: 41355c78f09eb1097e57a3c5d0ca11c9099e235e01ea5fa4e3953562a79a6a9296c1d300f1ba50ca75236048829e056b00685cd2f1ff8285e56fd2ce01249acb + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 languageName: node linkType: hard -"@jest/types@npm:^29.3.1": - version: 29.3.1 - resolution: "@jest/types@npm:29.3.1" +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" dependencies: - "@jest/schemas": ^29.0.0 + "@jest/schemas": ^29.6.3 "@types/istanbul-lib-coverage": ^2.0.0 "@types/istanbul-reports": ^3.0.0 "@types/node": "*" "@types/yargs": ^17.0.8 chalk: ^4.0.0 - checksum: 6f9faf27507b845ff3839c1adc6dbd038d7046d03d37e84c9fc956f60718711a801a5094c7eeee6b39ccf42c0ab61347fdc0fa49ab493ae5a8efd2fd41228ee8 + checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" dependencies: - "@jridgewell/set-array": ^1.0.1 + "@jridgewell/set-array": ^1.2.1 "@jridgewell/sourcemap-codec": ^1.4.10 - "@jridgewell/trace-mapping": ^0.3.9 - checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: ff7a1764ebd76a5e129c8890aa3e2f46045109dabde62b0b6c6a250152227647178ff2069ea234753a690d8f3c4ac8b5e7b267bbee272bffb7f3b0a370ab6e52 languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0": - version: 3.1.0 - resolution: "@jridgewell/resolve-uri@npm:3.1.0" - checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 83b85f72c59d1c080b4cbec0fef84528963a1b5db34e4370fa4bd1e3ff64a0d80e0cee7369d11d73c704e0286fb2865b530acac7a871088fbe92b5edf1000870 languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.1": - version: 1.1.2 - resolution: "@jridgewell/set-array@npm:1.1.2" - checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 languageName: node linkType: hard @@ -2328,20 +2347,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.4.14 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" - checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 05df4f2538b3b0f998ea4c1cd34574d0feba216fa5d4ccaef0187d12abf82eafe6021cec8b49f9bb4d90f2ba4582ccc581e72986a5fcf4176ae0cfeb04cf52ec languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.18 - resolution: "@jridgewell/trace-mapping@npm:0.3.18" +"@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: - "@jridgewell/resolve-uri": 3.1.0 - "@jridgewell/sourcemap-codec": 1.4.14 - checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: 9d3c40d225e139987b50c48988f8717a54a8c994d8a948ee42e1412e08988761d0754d7d10b803061cc3aebf35f92a5dbbab493bd0e1a9ef9e89a2130e83ba34 languageName: node linkType: hard @@ -2499,10 +2518,10 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.24.1": - version: 0.24.51 - resolution: "@sinclair/typebox@npm:0.24.51" - checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0 +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 languageName: node linkType: hard @@ -2540,159 +2559,159 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" +"@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cab83832830a57735329ed68f67c03b57ca21fa037b0134847b0c5c0ef4beca89956d7dacfbf7b2a10fd901e7009e877512086db2ee918b8c69aee7742ae32c0 + checksum: 3fc8e35d16f5abe0af5efe5851f27581225ac405d6a1ca44cda0df064cddfcc29a428c48c2e4bef6cebf627c9ac2f652a096030edb02cf5a120ce28d3c234710 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-attribute@npm:*": - version: 6.5.0 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.5.0" +"@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7a4dfc1345f5855b010684e9c5301731842bf91d72b82ce5cc4c82c80b94de1036e447a8a00fb306a6dd575cb4c640d8ce3cfee6607ddbb804796a77284c7f22 + checksum: ff992893c6c4ac802713ba3a97c13be34e62e6d981c813af40daabcd676df68a72a61bd1e692bb1eda3587f1b1d700ea462222ae2153bb0f46886632d4f88d08 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:*": - version: 6.5.0 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.5.0" +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3e173f720d530f9f71f8506f3eb78583eec3d87d66e385efe1ef3b3ebfc4e3680ec30f36414726de6a163e99ca69f54886022967e49476dea522267e1986936e + checksum: 0fb691b63a21bac00da3aa2dccec50d0d5a5b347ff408d60803b84410d8af168f2656e4ba1ee1f24dab0ae4e4af77901f2928752bb0434c1f6788133ec599ec8 languageName: node linkType: hard -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.1" +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b7d2125758e766e1ebd14b92216b800bdc976959bc696dbfa1e28682919147c1df4bb8b1b5fd037d7a83026e27e681fea3b8d3741af8d3cf4c9dfa3d412125df + checksum: 1edda65ef4f4dd8f021143c8ec276a08f6baa6f733b8e8ee2e7775597bf6b97afb47fdeefd579d6ae6c959fe2e634f55cd61d99377631212228c8cfb351b8921 languageName: node linkType: hard -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.1" +"@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0fd42ebf127ae9163ef341e84972daa99bdcb9e6ed3f83aabd95ee173fddc43e40e02fa847fbc0a1058cf5549f72b7960a2c5e22c3e4ac18f7e3ac81277852ae + checksum: 876cec891488992e6a9aebb8155e2bea4ec461b4718c51de36e988e00e271c6d9d01ef6be17b9effd44b2b3d7db0b41c161a5904a46ae6f38b26b387ad7f3709 languageName: node linkType: hard -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.1" +"@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c1550ee9f548526fa66fd171e3ffb5696bfc4e4cd108a631d39db492c7410dc10bba4eb5a190e9df824bf806130ccc586ae7d2e43c547e6a4f93bbb29a18f344 + checksum: be0e2d391164428327d9ec469a52cea7d93189c6b0e2c290999e048f597d777852f701c64dca44cd45b31ed14a7f859520326e2e4ad7c3a4545d0aa235bc7e9a languageName: node linkType: hard -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.1" +"@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4c924af22b948b812629e80efb90ad1ec8faae26a232d8ca8a06b46b53e966a2c415a57806a3ff0ea806a622612e546422719b69ec6839717a7755dac19171d9 + checksum: 85b434a57572f53bd2b9f0606f253e1fcf57b4a8c554ec3f2d43ed17f50d8cae200cb3aaf1ec9d626e1456e8b135dce530ae047eb0bed6d4bf98a752d6640459 languageName: node linkType: hard -"@svgr/babel-plugin-transform-svg-component@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.1" +"@svgr/babel-plugin-transform-svg-component@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:8.0.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e496bb5ee871feb6bcab250b6e067322da7dd5c9c2b530b41e5586fe090f86611339b49d0a909c334d9b24cbca0fa755c949a2526c6ad03c6b5885666874cf5f + checksum: 04e2023d75693eeb0890341c40e449881184663056c249be7e5c80168e4aabb0fadd255e8d5d2dbf54b8c2a6e700efba994377135bfa4060dc4a2e860116ef8c languageName: node linkType: hard -"@svgr/babel-preset@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-preset@npm:6.5.1" +"@svgr/babel-preset@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-preset@npm:8.1.0" dependencies: - "@svgr/babel-plugin-add-jsx-attribute": ^6.5.1 - "@svgr/babel-plugin-remove-jsx-attribute": "*" - "@svgr/babel-plugin-remove-jsx-empty-expression": "*" - "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.1 - "@svgr/babel-plugin-svg-dynamic-title": ^6.5.1 - "@svgr/babel-plugin-svg-em-dimensions": ^6.5.1 - "@svgr/babel-plugin-transform-react-native-svg": ^6.5.1 - "@svgr/babel-plugin-transform-svg-component": ^6.5.1 + "@svgr/babel-plugin-add-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": 8.0.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": 8.0.0 + "@svgr/babel-plugin-svg-dynamic-title": 8.0.0 + "@svgr/babel-plugin-svg-em-dimensions": 8.0.0 + "@svgr/babel-plugin-transform-react-native-svg": 8.1.0 + "@svgr/babel-plugin-transform-svg-component": 8.0.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9f124be39a8e64f909162f925b3a63ddaa5a342a5e24fc0b7f7d9d4d7f7e3b916596c754fb557dc259928399cad5366a27cb231627a0d2dcc4b13ac521cf05af + checksum: 3a67930f080b8891e1e8e2595716b879c944d253112bae763dce59807ba23454d162216c8d66a0a0e3d4f38a649ecd6c387e545d1e1261dd69a68e9a3392ee08 languageName: node linkType: hard -"@svgr/core@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/core@npm:6.5.1" +"@svgr/core@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/core@npm:8.1.0" dependencies: - "@babel/core": ^7.19.6 - "@svgr/babel-preset": ^6.5.1 - "@svgr/plugin-jsx": ^6.5.1 + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 camelcase: ^6.2.0 - cosmiconfig: ^7.0.1 - checksum: fd6d6d5da5aeb956703310480b626c1fb3e3973ad9fe8025efc1dcf3d895f857b70d100c63cf32cebb20eb83c9607bafa464c9436e18fe6fe4fafdc73ed6b1a5 + cosmiconfig: ^8.1.3 + snake-case: ^3.0.4 + checksum: da4a12865c7dc59829d58df8bd232d6c85b7115fda40da0d2f844a1a51886e2e945560596ecfc0345d37837ac457de86a931e8b8d8550e729e0c688c02250d8a languageName: node linkType: hard -"@svgr/hast-util-to-babel-ast@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.1" +"@svgr/hast-util-to-babel-ast@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:8.0.0" dependencies: - "@babel/types": ^7.20.0 + "@babel/types": ^7.21.3 entities: ^4.4.0 - checksum: 37923cce1b3f4e2039077b0c570b6edbabe37d1cf1a6ee35e71e0fe00f9cffac450eec45e9720b1010418131a999cb0047331ba1b6d1d2c69af1b92ac785aacf + checksum: 88401281a38bbc7527e65ff5437970414391a86158ef4b4046c89764c156d2d39ecd7cce77be8a51994c9fb3249170cb1eb8b9128b62faaa81743ef6ed3534ab languageName: node linkType: hard -"@svgr/plugin-jsx@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/plugin-jsx@npm:6.5.1" +"@svgr/plugin-jsx@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-jsx@npm:8.1.0" dependencies: - "@babel/core": ^7.19.6 - "@svgr/babel-preset": ^6.5.1 - "@svgr/hast-util-to-babel-ast": ^6.5.1 + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 + "@svgr/hast-util-to-babel-ast": 8.0.0 svg-parser: ^2.0.4 peerDependencies: - "@svgr/core": ^6.0.0 - checksum: 42f22847a6bdf930514d7bedd3c5e1fd8d53eb3594779f9db16cb94c762425907c375cd8ec789114e100a4d38068aca6c7ab5efea4c612fba63f0630c44cc859 + "@svgr/core": "*" + checksum: 0418a9780753d3544912ee2dad5d2cf8d12e1ba74df8053651b3886aeda54d5f0f7d2dece0af5e0d838332c4f139a57f0dabaa3ca1afa4d1a765efce6a7656f2 languageName: node linkType: hard -"@svgr/plugin-svgo@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/plugin-svgo@npm:6.5.1" +"@svgr/plugin-svgo@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-svgo@npm:8.1.0" dependencies: - cosmiconfig: ^7.0.1 - deepmerge: ^4.2.2 - svgo: ^2.8.0 + cosmiconfig: ^8.1.3 + deepmerge: ^4.3.1 + svgo: ^3.0.2 peerDependencies: "@svgr/core": "*" - checksum: cd2833530ac0485221adc2146fd992ab20d79f4b12eebcd45fa859721dd779483158e11dfd9a534858fe468416b9412416e25cbe07ac7932c44ed5fa2021c72e + checksum: 59d9d214cebaacca9ca71a561f463d8b7e5a68ca9443e4792a42d903acd52259b1790c0680bc6afecc3f00a255a6cbd7ea278a9f625bac443620ea58a590c2d0 languageName: node linkType: hard -"@svgr/webpack@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/webpack@npm:6.5.1" +"@svgr/webpack@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/webpack@npm:8.1.0" dependencies: - "@babel/core": ^7.19.6 - "@babel/plugin-transform-react-constant-elements": ^7.18.12 - "@babel/preset-env": ^7.19.4 + "@babel/core": ^7.21.3 + "@babel/plugin-transform-react-constant-elements": ^7.21.3 + "@babel/preset-env": ^7.20.2 "@babel/preset-react": ^7.18.6 - "@babel/preset-typescript": ^7.18.6 - "@svgr/core": ^6.5.1 - "@svgr/plugin-jsx": ^6.5.1 - "@svgr/plugin-svgo": ^6.5.1 - checksum: d10582eb4fa82a5b6d314cb49f2c640af4fd3a60f5b76095d2b14e383ef6a43a6f4674b68774a21787dbde69dec0a251cfcfc3f9a96c82754ba5d5c6daf785f0 + "@babel/preset-typescript": ^7.21.0 + "@svgr/core": 8.1.0 + "@svgr/plugin-jsx": 8.1.0 + "@svgr/plugin-svgo": 8.1.0 + checksum: c6eec5b0cf2fb2ecd3a7a362d272eda35330b17c76802a3481f499b5d07ff8f87b31d2571043bff399b051a1767b1e2e499dbf186104d1c06d76f9f1535fac01 languageName: node linkType: hard @@ -3553,7 +3572,7 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^5.0.0": +"ajv-keywords@npm:^5.1.0": version: 5.1.0 resolution: "ajv-keywords@npm:5.1.0" dependencies: @@ -3576,15 +3595,15 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.8.0": - version: 8.11.2 - resolution: "ajv@npm:8.11.2" +"ajv@npm:^8.0.0, ajv@npm:^8.9.0": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" dependencies: - fast-deep-equal: ^3.1.1 + fast-deep-equal: ^3.1.3 + fast-uri: ^3.0.1 json-schema-traverse: ^1.0.0 require-from-string: ^2.0.2 - uri-js: ^4.2.2 - checksum: 53435bf79ee7d1eabba8085962dba4c08d08593334b304db7772887f0b7beebc1b3d957432f7437ed4b60e53b5d966a57b439869890209c50fed610459999e3e + checksum: 1797bf242cfffbaf3b870d13565bd1716b73f214bb7ada9a497063aada210200da36e3ed40237285f3255acc4feeae91b1fb183625331bad27da95973f7253d9 languageName: node linkType: hard @@ -3765,21 +3784,21 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^10.4.12, autoprefixer@npm:^10.4.14": - version: 10.4.16 - resolution: "autoprefixer@npm:10.4.16" +"autoprefixer@npm:^10.4.14, autoprefixer@npm:^10.4.19": + version: 10.4.20 + resolution: "autoprefixer@npm:10.4.20" dependencies: - browserslist: ^4.21.10 - caniuse-lite: ^1.0.30001538 - fraction.js: ^4.3.6 + browserslist: ^4.23.3 + caniuse-lite: ^1.0.30001646 + fraction.js: ^4.3.7 normalize-range: ^0.1.2 - picocolors: ^1.0.0 + picocolors: ^1.0.1 postcss-value-parser: ^4.2.0 peerDependencies: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 45fad7086495048dacb14bb7b00313e70e135b5d8e8751dcc60548889400763705ab16fc2d99ea628b44c3472698fb0e39598f595ba28409c965ab159035afde + checksum: 187cec2ec356631932b212f76dc64f4419c117fdb2fb9eeeb40867d38ba5ca5ba734e6ceefc9e3af4eec8258e60accdf5cbf2b7708798598fde35cdc3de562d6 languageName: node linkType: hard @@ -3805,16 +3824,28 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.5": - version: 0.4.5 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.5" +"babel-plugin-polyfill-corejs2@npm:^0.4.10, babel-plugin-polyfill-corejs2@npm:^0.4.5": + version: 0.4.11 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.11" dependencies: "@babel/compat-data": ^7.22.6 - "@babel/helper-define-polyfill-provider": ^0.4.2 + "@babel/helper-define-polyfill-provider": ^0.6.2 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 33a8e06aa54e2858d211c743d179f0487b03222f9ca1bfd7c4865bca243fca942a3358cb75f6bb894ed476cbddede834811fbd6903ff589f055821146f053e1a + checksum: f098353ce7c7dde1a1d2710858e01b471e85689110c9e37813e009072347eb8c55d5f84d20d3bf1cab31755f20078ba90f8855fdc4686a9daa826a95ff280bd7 + languageName: node + linkType: hard + +"babel-plugin-polyfill-corejs3@npm:^0.10.6": + version: 0.10.6 + resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.6.2 + core-js-compat: ^3.38.0 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: f762f29f7acca576897c63149c850f0a72babd3fb9ea436a2e36f0c339161c4b912a77828541d8188ce8a91e50965c6687120cf36071eabb1b7aa92f279e2164 languageName: node linkType: hard @@ -3841,6 +3872,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-regenerator@npm:^0.6.1": + version: 0.6.2 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.2" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.6.2 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 150233571072b6b3dfe946242da39cba8587b7f908d1c006f7545fc88b0e3c3018d445739beb61e7a75835f0c2751dbe884a94ff9b245ec42369d9267e0e1b3f + languageName: node + linkType: hard + "backstage-microsite@workspace:.": version: 0.0.0-use.local resolution: "backstage-microsite@workspace:." @@ -3997,7 +4039,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": +"braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -4006,17 +4048,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.18.1, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.1, browserslist@npm:^4.22.2": - version: 4.22.3 - resolution: "browserslist@npm:4.22.3" +"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.18.1, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": + version: 4.23.3 + resolution: "browserslist@npm:4.23.3" dependencies: - caniuse-lite: ^1.0.30001580 - electron-to-chromium: ^1.4.648 - node-releases: ^2.0.14 - update-browserslist-db: ^1.0.13 + caniuse-lite: ^1.0.30001646 + electron-to-chromium: ^1.5.4 + node-releases: ^2.0.18 + update-browserslist-db: ^1.1.0 bin: browserslist: cli.js - checksum: e62b17348e92143fe58181b02a6a97c4a98bd812d1dc9274673a54f73eec53dbed1c855ebf73e318ee00ee039f23c9a6d0e7629d24f3baef08c7a5b469742d57 + checksum: 7906064f9970aeb941310b2fcb8b4ace4a1b50aa657c986677c6f1553a8cabcc94ee9c5922f715baffbedaa0e6cf0831b6fed7b059dde6873a4bfadcbe069c7e languageName: node linkType: hard @@ -4142,10 +4184,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001580": - version: 1.0.30001587 - resolution: "caniuse-lite@npm:1.0.30001587" - checksum: fb50aa9beaaae42f9feae92ce038f6ff71e97510f024ef1bef2666f3adcfd36d6c59e5675442e5fe795575193f71bc826cb7721d4b0f6d763e82d193bea57863 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001646": + version: 1.0.30001653 + resolution: "caniuse-lite@npm:1.0.30001653" + checksum: 289cf06c26a46f3e6460ccd5feffa788ab0ab35d306898c48120c65cfb11959bfa560e9f739393769b4fd01150c69b0747ad3ad5ec3abf3dfafd66df3c59254e languageName: node linkType: hard @@ -4233,7 +4275,7 @@ __metadata: languageName: node linkType: hard -"cheerio@npm:^1.0.0-rc.12": +"cheerio@npm:1.0.0-rc.12": version: 1.0.0-rc.12 resolution: "cheerio@npm:1.0.0-rc.12" dependencies: @@ -4390,7 +4432,7 @@ __metadata: languageName: node linkType: hard -"colord@npm:^2.9.1": +"colord@npm:^2.9.3": version: 2.9.3 resolution: "colord@npm:2.9.3" checksum: 95d909bfbcfd8d5605cbb5af56f2d1ce2b323990258fd7c0d2eb0e6d3bb177254d7fb8213758db56bb4ede708964f78c6b992b326615f81a18a6aaf11d64c650 @@ -4602,12 +4644,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.32.2": - version: 3.33.0 - resolution: "core-js-compat@npm:3.33.0" +"core-js-compat@npm:^3.32.2, core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": + version: 3.38.1 + resolution: "core-js-compat@npm:3.38.1" dependencies: - browserslist: ^4.22.1 - checksum: 83ae54008c09b8e0ae3c59457039866c342c7e28b0d30eebb638a5b51c01432e63fe97695c90645cbc6a8b073a4f9a8b0e75f0818bbf8b4b054e01f4c17d3181 + browserslist: ^4.23.3 + checksum: a0a5673bcd59f588f0cd0b59cdacd4712b82909738a87406d334dd412eb3d273ae72b275bdd8e8fef63fca9ef12b42ed651be139c7c44c8a1acb423c8906992e languageName: node linkType: hard @@ -4645,20 +4687,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^7.0.1": - version: 7.1.0 - resolution: "cosmiconfig@npm:7.1.0" - dependencies: - "@types/parse-json": ^4.0.0 - import-fresh: ^3.2.1 - parse-json: ^5.0.0 - path-type: ^4.0.0 - yaml: ^1.10.0 - checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f - languageName: node - linkType: hard - -"cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -4695,12 +4724,12 @@ __metadata: languageName: node linkType: hard -"css-declaration-sorter@npm:^6.3.1": - version: 6.3.1 - resolution: "css-declaration-sorter@npm:6.3.1" +"css-declaration-sorter@npm:^7.2.0": + version: 7.2.0 + resolution: "css-declaration-sorter@npm:7.2.0" peerDependencies: postcss: ^8.0.9 - checksum: ff0d9989ee21ec4c42430b9bb86c43f973ed5024d68f30edc1e3fb07a22828ce3c3e5b922019f2ccbff606722e43c407c5c76e3cddac523ac4afcb31e4b2601c + checksum: 69b2f63a1c7c593123fabcbb353618ed01eb75f6404da9321328fbb30d603d89c47195129fadf1dc316e1406a0881400b324c2bded9438c47196e1c96ec726dd languageName: node linkType: hard @@ -4722,16 +4751,16 @@ __metadata: languageName: node linkType: hard -"css-minimizer-webpack-plugin@npm:^4.2.2": - version: 4.2.2 - resolution: "css-minimizer-webpack-plugin@npm:4.2.2" +"css-minimizer-webpack-plugin@npm:^5.0.1": + version: 5.0.1 + resolution: "css-minimizer-webpack-plugin@npm:5.0.1" dependencies: - cssnano: ^5.1.8 - jest-worker: ^29.1.2 - postcss: ^8.4.17 - schema-utils: ^4.0.0 - serialize-javascript: ^6.0.0 - source-map: ^0.6.1 + "@jridgewell/trace-mapping": ^0.3.18 + cssnano: ^6.0.1 + jest-worker: ^29.4.3 + postcss: ^8.4.24 + schema-utils: ^4.0.1 + serialize-javascript: ^6.0.1 peerDependencies: webpack: ^5.0.0 peerDependenciesMeta: @@ -4747,7 +4776,7 @@ __metadata: optional: true lightningcss: optional: true - checksum: 5417e76a445f35832aa96807c835b8e92834a6cd285b1b788dfe3ca0fa90fec7eb2dd6efa9d3649f9d8244b99b7da2d065951603b94918e8f6a366a5049cacdd + checksum: 10055802c61d1ae72584eac03b6bd221ecbefde11d337be44a5459d8de075b38f91b80949f95cd0c3a10295615ee013f82130bfac5fe9b5b3e8e75531f232680 languageName: node linkType: hard @@ -4777,13 +4806,23 @@ __metadata: languageName: node linkType: hard -"css-tree@npm:^1.1.2, css-tree@npm:^1.1.3": - version: 1.1.3 - resolution: "css-tree@npm:1.1.3" +"css-tree@npm:^2.3.1": + version: 2.3.1 + resolution: "css-tree@npm:2.3.1" dependencies: - mdn-data: 2.0.14 - source-map: ^0.6.1 - checksum: 79f9b81803991b6977b7fcb1588799270438274d89066ce08f117f5cdb5e20019b446d766c61506dd772c839df84caa16042d6076f20c97187f5abe3b50e7d1f + mdn-data: 2.0.30 + source-map-js: ^1.0.1 + checksum: 493cc24b5c22b05ee5314b8a0d72d8a5869491c1458017ae5ed75aeb6c3596637dbe1b11dac2548974624adec9f7a1f3a6cf40593dc1f9185eb0e8279543fbc0 + languageName: node + linkType: hard + +"css-tree@npm:~2.2.0": + version: 2.2.1 + resolution: "css-tree@npm:2.2.1" + dependencies: + mdn-data: 2.0.28 + source-map-js: ^1.0.1 + checksum: b94aa8cc2f09e6f66c91548411fcf74badcbad3e150345074715012d16333ce573596ff5dfca03c2a87edf1924716db765120f94247e919d72753628ba3aba27 languageName: node linkType: hard @@ -4803,89 +4842,90 @@ __metadata: languageName: node linkType: hard -"cssnano-preset-advanced@npm:^5.3.10": - version: 5.3.10 - resolution: "cssnano-preset-advanced@npm:5.3.10" +"cssnano-preset-advanced@npm:^6.1.2": + version: 6.1.2 + resolution: "cssnano-preset-advanced@npm:6.1.2" dependencies: - autoprefixer: ^10.4.12 - cssnano-preset-default: ^5.2.14 - postcss-discard-unused: ^5.1.0 - postcss-merge-idents: ^5.1.1 - postcss-reduce-idents: ^5.2.0 - postcss-zindex: ^5.1.0 + autoprefixer: ^10.4.19 + browserslist: ^4.23.0 + cssnano-preset-default: ^6.1.2 + postcss-discard-unused: ^6.0.5 + postcss-merge-idents: ^6.0.3 + postcss-reduce-idents: ^6.0.3 + postcss-zindex: ^6.0.2 peerDependencies: - postcss: ^8.2.15 - checksum: d21cb382aea2f35c9eaa50686280bbd5158260edf73020731364b03bae0d887292da51ed0b20b369f51d2573ee8c02c695f604647b839a9ca746be8a44c3bb5b + postcss: ^8.4.31 + checksum: cf70e27915947412730abb3075587efb66bcea58d7f1b906a7225bb4a40c9ca40150251a2ac33363d4f55bbdeb9ba000c242fa6244ee36cba2477ac07fbbe791 languageName: node linkType: hard -"cssnano-preset-default@npm:^5.2.14": - version: 5.2.14 - resolution: "cssnano-preset-default@npm:5.2.14" +"cssnano-preset-default@npm:^6.1.2": + version: 6.1.2 + resolution: "cssnano-preset-default@npm:6.1.2" dependencies: - css-declaration-sorter: ^6.3.1 - cssnano-utils: ^3.1.0 - postcss-calc: ^8.2.3 - postcss-colormin: ^5.3.1 - postcss-convert-values: ^5.1.3 - postcss-discard-comments: ^5.1.2 - postcss-discard-duplicates: ^5.1.0 - postcss-discard-empty: ^5.1.1 - postcss-discard-overridden: ^5.1.0 - postcss-merge-longhand: ^5.1.7 - postcss-merge-rules: ^5.1.4 - postcss-minify-font-values: ^5.1.0 - postcss-minify-gradients: ^5.1.1 - postcss-minify-params: ^5.1.4 - postcss-minify-selectors: ^5.2.1 - postcss-normalize-charset: ^5.1.0 - postcss-normalize-display-values: ^5.1.0 - postcss-normalize-positions: ^5.1.1 - postcss-normalize-repeat-style: ^5.1.1 - postcss-normalize-string: ^5.1.0 - postcss-normalize-timing-functions: ^5.1.0 - postcss-normalize-unicode: ^5.1.1 - postcss-normalize-url: ^5.1.0 - postcss-normalize-whitespace: ^5.1.1 - postcss-ordered-values: ^5.1.3 - postcss-reduce-initial: ^5.1.2 - postcss-reduce-transforms: ^5.1.0 - postcss-svgo: ^5.1.0 - postcss-unique-selectors: ^5.1.1 + browserslist: ^4.23.0 + css-declaration-sorter: ^7.2.0 + cssnano-utils: ^4.0.2 + postcss-calc: ^9.0.1 + postcss-colormin: ^6.1.0 + postcss-convert-values: ^6.1.0 + postcss-discard-comments: ^6.0.2 + postcss-discard-duplicates: ^6.0.3 + postcss-discard-empty: ^6.0.3 + postcss-discard-overridden: ^6.0.2 + postcss-merge-longhand: ^6.0.5 + postcss-merge-rules: ^6.1.1 + postcss-minify-font-values: ^6.1.0 + postcss-minify-gradients: ^6.0.3 + postcss-minify-params: ^6.1.0 + postcss-minify-selectors: ^6.0.4 + postcss-normalize-charset: ^6.0.2 + postcss-normalize-display-values: ^6.0.2 + postcss-normalize-positions: ^6.0.2 + postcss-normalize-repeat-style: ^6.0.2 + postcss-normalize-string: ^6.0.2 + postcss-normalize-timing-functions: ^6.0.2 + postcss-normalize-unicode: ^6.1.0 + postcss-normalize-url: ^6.0.2 + postcss-normalize-whitespace: ^6.0.2 + postcss-ordered-values: ^6.0.2 + postcss-reduce-initial: ^6.1.0 + postcss-reduce-transforms: ^6.0.2 + postcss-svgo: ^6.0.3 + postcss-unique-selectors: ^6.0.4 peerDependencies: - postcss: ^8.2.15 - checksum: d3bbbe3d50c6174afb28d0bdb65b511fdab33952ec84810aef58b87189f3891c34aaa8b6a6101acd5314f8acded839b43513e39a75f91a698ddc985a1b1d9e95 + postcss: ^8.4.31 + checksum: 51d93e52df7141143947dc4695b5087c04b41ea153e4f4c0282ac012b62c7457c6aca244f604ae94fa3b4840903a30a1e7df38f8610e0b304d05e3065375ee56 languageName: node linkType: hard -"cssnano-utils@npm:^3.1.0": - version: 3.1.0 - resolution: "cssnano-utils@npm:3.1.0" +"cssnano-utils@npm:^4.0.2": + version: 4.0.2 + resolution: "cssnano-utils@npm:4.0.2" peerDependencies: - postcss: ^8.2.15 - checksum: 975c84ce9174cf23bb1da1e9faed8421954607e9ea76440cd3bb0c1bea7e17e490d800fca5ae2812d1d9e9d5524eef23ede0a3f52497d7ccc628e5d7321536f2 + postcss: ^8.4.31 + checksum: f04c6854e75d847c7a43aff835e003d5bc7387ddfc476f0ad3a2d63663d0cec41047d46604c1717bf6b5a8e24e54bb519e465ff78d62c7e073c7cbe2279bebaf languageName: node linkType: hard -"cssnano@npm:^5.1.15, cssnano@npm:^5.1.8": - version: 5.1.15 - resolution: "cssnano@npm:5.1.15" +"cssnano@npm:^6.0.1, cssnano@npm:^6.1.2": + version: 6.1.2 + resolution: "cssnano@npm:6.1.2" dependencies: - cssnano-preset-default: ^5.2.14 - lilconfig: ^2.0.3 - yaml: ^1.10.2 + cssnano-preset-default: ^6.1.2 + lilconfig: ^3.1.1 peerDependencies: - postcss: ^8.2.15 - checksum: ca9e1922178617c66c2f1548824b2c7af2ecf69cc3a187fc96bf8d29251c2e84d9e4966c69cf64a2a6a057a37dff7d6d057bc8a2a0957e6ea382e452ae9d0bbb + postcss: ^8.4.31 + checksum: 65aad92c5ee0089ffd4cd933c18c65edbf7634f7c3cd833a499dc948aa7e4168be22130dfe83bde07fcdc87f7c45a02d09040b7f439498208bc90b8d5a9abcc8 languageName: node linkType: hard -"csso@npm:^4.2.0": - version: 4.2.0 - resolution: "csso@npm:4.2.0" +"csso@npm:^5.0.5": + version: 5.0.5 + resolution: "csso@npm:5.0.5" dependencies: - css-tree: ^1.1.2 - checksum: 380ba9663da3bcea58dee358a0d8c4468bb6539be3c439dc266ac41c047217f52fd698fb7e4b6b6ccdfb8cf53ef4ceed8cc8ceccb8dfca2aa628319826b5b998 + css-tree: ~2.2.0 + checksum: 0ad858d36bf5012ed243e9ec69962a867509061986d2ee07cc040a4b26e4d062c00d4c07e5ba8d430706ceb02dd87edd30a52b5937fd45b1b6f2119c4993d59a languageName: node linkType: hard @@ -4942,10 +4982,10 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2": - version: 4.2.2 - resolution: "deepmerge@npm:4.2.2" - checksum: a8c43a1ed8d6d1ed2b5bf569fa4c8eb9f0924034baf75d5d406e47e157a451075c4db353efea7b6bcc56ec48116a8ce72fccf867b6e078e7c561904b5897530b +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 languageName: node linkType: hard @@ -5239,10 +5279,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.648": - version: 1.4.665 - resolution: "electron-to-chromium@npm:1.4.665" - checksum: 6c863300909a2712a79bd8a00ff2d3747fb72edf3dfda892449aabd42a12448e8e215e318d628421bbdfc7e4e3bc1fb38a81bbd530a71036bb86a75868e9eb8a +"electron-to-chromium@npm:^1.5.4": + version: 1.5.13 + resolution: "electron-to-chromium@npm:1.5.13" + checksum: f18ac84dd3bf9a200654a6a9292b9ec4bced0cf9bd26cec9941b775f4470c581c9d043e70b37a124d9752dcc0f47fc96613d52b2defd8e59632852730cb418b9 languageName: node linkType: hard @@ -5351,10 +5391,10 @@ __metadata: languageName: node linkType: hard -"escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: a3e2a99f07acb74b3ad4989c48ca0c3140f69f923e56d0cba0526240ee470b91010f9d39001f2a4a313841d237ede70a729e92125191ba5d21e74b106800b133 +"escalade@npm:^3.1.1, escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 1ec0977aa2772075493002bdbd549d595ff6e9393b1cb0d7d6fcaf78c750da0c158f180938365486f75cb69fba20294351caddfce1b46552a7b6c3cde52eaa02 languageName: node linkType: hard @@ -5648,6 +5688,13 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^3.0.1": + version: 3.0.1 + resolution: "fast-uri@npm:3.0.1" + checksum: 106143ff83705995225dcc559411288f3337e732bb2e264e79788f1914b6bd8f8bc3683102de60b15ba00e6ebb443633cabac77d4ebc5cb228c47cf955e199ff + languageName: node + linkType: hard + "fast-url-parser@npm:1.1.3": version: 1.1.3 resolution: "fast-url-parser@npm:1.1.3" @@ -5837,10 +5884,10 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^4.3.6": - version: 4.3.6 - resolution: "fraction.js@npm:4.3.6" - checksum: e96ae77e64ebfd442d3a5a01a3f0637b0663fc2440bcf2841b3ad9341ba24c81fb2e3e7142e43ef7d088558c6b3f8609df135b201adc7a1c674aea6a71384162 +"fraction.js@npm:^4.3.7": + version: 4.3.7 + resolution: "fraction.js@npm:4.3.7" + checksum: e1553ae3f08e3ba0e8c06e43a3ab20b319966dfb7ddb96fd9b5d0ee11a66571af7f993229c88ebbb0d4a816eb813a24ed48207b140d442a8f76f33763b8d1f3f languageName: node linkType: hard @@ -5851,14 +5898,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.1": - version: 11.1.1 - resolution: "fs-extra@npm:11.1.1" +"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" dependencies: graceful-fs: ^4.2.0 jsonfile: ^6.0.1 universalify: ^2.0.0 - checksum: fb883c68245b2d777fbc1f2082c9efb084eaa2bbf9fddaa366130d196c03608eebef7fb490541276429ee1ca99f317e2d73e96f5ca0999eefedf5a624ae1edfd + checksum: b12e42fa40ba47104202f57b8480dd098aa931c2724565e5e70779ab87605665594e76ee5fb00545f772ab9ace167fe06d2ab009c416dc8c842c5ae6df7aa7e8 languageName: node linkType: hard @@ -6649,7 +6696,7 @@ __metadata: languageName: node linkType: hard -"import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": +"import-fresh@npm:^3.1.0, import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" dependencies: @@ -6687,10 +6734,10 @@ __metadata: languageName: node linkType: hard -"infima@npm:0.2.0-alpha.43": - version: 0.2.0-alpha.43 - resolution: "infima@npm:0.2.0-alpha.43" - checksum: fc5f79240e940eddd750439511767092ccb4051e5e91d253ec7630a9e7ce691812da3aa0f05e46b4c0a95dbfadeae5714fd0073f8d2df12e5aaff0697a1d6aa2 +"infima@npm:0.2.0-alpha.44": + version: 0.2.0-alpha.44 + resolution: "infima@npm:0.2.0-alpha.44" + checksum: e9871f4056c0c8b311fcd32e2864d23a8f6807af5ff32d3c4d8271ad9971b5a7ea5016787a6b215893bb3e9f5f14326816bc05151d576dd375b0d79279cdfa8b languageName: node linkType: hard @@ -7066,17 +7113,17 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.3.1": - version: 29.3.1 - resolution: "jest-util@npm:29.3.1" +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" dependencies: - "@jest/types": ^29.3.1 + "@jest/types": ^29.6.3 "@types/node": "*" chalk: ^4.0.0 ci-info: ^3.2.0 graceful-fs: ^4.2.9 picomatch: ^2.2.3 - checksum: f67c60f062b94d21cb60e84b3b812d64b7bfa81fe980151de5c17a74eb666042d0134e2e756d099b7606a1fcf1d633824d2e58197d01d76dde1e2dc00dfcd413 + checksum: 042ab4980f4ccd4d50226e01e5c7376a8556b472442ca6091a8f102488c0f22e6e8b89ea874111d2328a2080083bf3225c86f3788c52af0bd0345a00eb57a3ca languageName: node linkType: hard @@ -7091,15 +7138,15 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^29.1.2": - version: 29.3.1 - resolution: "jest-worker@npm:29.3.1" +"jest-worker@npm:^29.4.3": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" dependencies: "@types/node": "*" - jest-util: ^29.3.1 + jest-util: ^29.7.0 merge-stream: ^2.0.0 supports-color: ^8.0.0 - checksum: 38687fcbdc2b7ddc70bbb5dfc703ae095b46b3c7f206d62ecdf5f4d16e336178e217302138f3b906125576bb1cfe4cfe8d43681276fa5899d138ed9422099fb3 + checksum: 30fff60af49675273644d408b650fc2eb4b5dcafc5a0a455f238322a8f9d8a98d847baca9d51ff197b6747f54c7901daa2287799230b856a0f48287d131f8c13 languageName: node linkType: hard @@ -7279,10 +7326,10 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^2.0.3": - version: 2.0.6 - resolution: "lilconfig@npm:2.0.6" - checksum: 40a3cd72f103b1be5975f2ac1850810b61d4053e20ab09be8d3aeddfe042187e1ba70b4651a7e70f95efa1642e7dc8b2ae395b317b7d7753b241b43cef7c0f7d +"lilconfig@npm:^3.1.1": + version: 3.1.2 + resolution: "lilconfig@npm:3.1.2" + checksum: 4e8b83ddd1d0ad722600994e6ba5d858ddca14f0587aa6b9c8185e17548149b5e13d4d583d811e9e9323157fa8c6a527e827739794c7502b59243c58e210b8c3 languageName: node linkType: hard @@ -7766,10 +7813,17 @@ __metadata: languageName: node linkType: hard -"mdn-data@npm:2.0.14": - version: 2.0.14 - resolution: "mdn-data@npm:2.0.14" - checksum: 9d0128ed425a89f4cba8f787dca27ad9408b5cb1b220af2d938e2a0629d17d879a34d2cb19318bdb26c3f14c77dd5dfbae67211f5caaf07b61b1f2c5c8c7dc16 +"mdn-data@npm:2.0.28": + version: 2.0.28 + resolution: "mdn-data@npm:2.0.28" + checksum: f51d587a6ebe8e426c3376c74ea6df3e19ec8241ed8e2466c9c8a3904d5d04397199ea4f15b8d34d14524b5de926d8724ae85207984be47e165817c26e49e0aa + languageName: node + linkType: hard + +"mdn-data@npm:2.0.30": + version: 2.0.30 + resolution: "mdn-data@npm:2.0.30" + checksum: d6ac5ac7439a1607df44b22738ecf83f48e66a0874e4482d6424a61c52da5cde5750f1d1229b6f5fa1b80a492be89465390da685b11f97d62b8adcc6e88189aa languageName: node linkType: hard @@ -8316,12 +8370,12 @@ __metadata: linkType: hard "micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": - version: 4.0.5 - resolution: "micromatch@npm:4.0.5" + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" dependencies: - braces: ^3.0.2 + braces: ^3.0.3 picomatch: ^2.3.1 - checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc + checksum: 79920eb634e6f400b464a954fcfa589c4e7c7143209488e44baf627f9affc8b1e306f41f4f0deedde97e69cb725920879462d3e750ab3bd3c1aed675bb3a8966 languageName: node linkType: hard @@ -8556,12 +8610,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6": - version: 3.3.6 - resolution: "nanoid@npm:3.3.6" +"nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" bin: nanoid: bin/nanoid.cjs - checksum: 7d0eda657002738aa5206107bd0580aead6c95c460ef1bdd0b1a87a9c7ae6277ac2e9b945306aaa5b32c6dcb7feaf462d0f552e7f8b5718abfc6ead5c94a71b3 + checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2 languageName: node linkType: hard @@ -8628,10 +8682,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.14": - version: 2.0.14 - resolution: "node-releases@npm:2.0.14" - checksum: 59443a2f77acac854c42d321bf1b43dea0aef55cd544c6a686e9816a697300458d4e82239e2d794ea05f7bbbc8a94500332e2d3ac3f11f52e4b16cbe638b3c41 +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: ef55a3d853e1269a6d6279b7692cd6ff3e40bc74947945101138745bfdc9a5edabfe72cb19a31a8e45752e1910c4c65c77d931866af6357f242b172b7283f5b3 languageName: node linkType: hard @@ -8660,13 +8714,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:^6.0.1": - version: 6.1.0 - resolution: "normalize-url@npm:6.1.0" - checksum: 4a4944631173e7d521d6b80e4c85ccaeceb2870f315584fa30121f505a6dfd86439c5e3fdd8cd9e0e291290c41d0c3599f0cb12ab356722ed242584c30348e50 - languageName: node - linkType: hard - "normalize-url@npm:^8.0.0": version: 8.0.0 resolution: "normalize-url@npm:8.0.0" @@ -9084,10 +9131,10 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0": - version: 1.0.0 - resolution: "picocolors@npm:1.0.0" - checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 languageName: node linkType: hard @@ -9116,88 +9163,88 @@ __metadata: languageName: node linkType: hard -"postcss-calc@npm:^8.2.3": - version: 8.2.4 - resolution: "postcss-calc@npm:8.2.4" +"postcss-calc@npm:^9.0.1": + version: 9.0.1 + resolution: "postcss-calc@npm:9.0.1" dependencies: - postcss-selector-parser: ^6.0.9 + postcss-selector-parser: ^6.0.11 postcss-value-parser: ^4.2.0 peerDependencies: postcss: ^8.2.2 - checksum: 314b4cebb0c4ed0cf8356b4bce71eca78f5a7842e6a3942a3bba49db168d5296b2bd93c3f735ae1c616f2651d94719ade33becc03c73d2d79c7394fb7f73eabb + checksum: 7327ed83bfec544ab8b3e38353baa72ff6d04378b856db4ad82dbd68ce0b73668867ef182b5d4025f9dd9aa9c64aacc50cd1bd9db8d8b51ccc4cb97866b9d72b languageName: node linkType: hard -"postcss-colormin@npm:^5.3.1": - version: 5.3.1 - resolution: "postcss-colormin@npm:5.3.1" +"postcss-colormin@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-colormin@npm:6.1.0" dependencies: - browserslist: ^4.21.4 + browserslist: ^4.23.0 caniuse-api: ^3.0.0 - colord: ^2.9.1 + colord: ^2.9.3 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: e5778baab30877cd1f51e7dc9d2242a162aeca6360a52956acd7f668c5bc235c2ccb7e4df0370a804d65ebe00c5642366f061db53aa823f9ed99972cebd16024 + postcss: ^8.4.31 + checksum: 55a1525de345d953bc7f32ecaa5ee6275ef0277c27d1f97ff06a1bd1a2fedf7f254e36dc1500621f1df20c25a6d2485a74a0b527d8ff74eb90726c76efe2ac8e languageName: node linkType: hard -"postcss-convert-values@npm:^5.1.3": - version: 5.1.3 - resolution: "postcss-convert-values@npm:5.1.3" +"postcss-convert-values@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-convert-values@npm:6.1.0" dependencies: - browserslist: ^4.21.4 + browserslist: ^4.23.0 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: df48cdaffabf9737f9cfdc58a3dc2841cf282506a7a944f6c70236cff295d3a69f63de6e0935eeb8a9d3f504324e5b4e240abc29e21df9e35a02585d3060aeb5 + postcss: ^8.4.31 + checksum: 43e9f66af9bdec3c76695f9dde36885abc01f662c370c490b45d895459caab2c5792f906f3ddad107129133e41485a65634da7f699eef916a636e47f6a37a299 languageName: node linkType: hard -"postcss-discard-comments@npm:^5.1.2": - version: 5.1.2 - resolution: "postcss-discard-comments@npm:5.1.2" +"postcss-discard-comments@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-discard-comments@npm:6.0.2" peerDependencies: - postcss: ^8.2.15 - checksum: abfd064ebc27aeaf5037643dd51ffaff74d1fa4db56b0523d073ace4248cbb64ffd9787bd6924b0983a9d0bd0e9bf9f10d73b120e50391dc236e0d26c812fa2a + postcss: ^8.4.31 + checksum: c1731ccc8d1e3d910412a61395988d3033365e6532d9e5432ad7c74add8c9dcb0af0c03d4e901bf0d2b59ea4e7297a0c77a547ff2ed1b1cc065559cc0de43b4e languageName: node linkType: hard -"postcss-discard-duplicates@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-discard-duplicates@npm:5.1.0" +"postcss-discard-duplicates@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-discard-duplicates@npm:6.0.3" peerDependencies: - postcss: ^8.2.15 - checksum: 88d6964201b1f4ed6bf7a32cefe68e86258bb6e42316ca01d9b32bdb18e7887d02594f89f4a2711d01b51ea6e3fcca8c54be18a59770fe5f4521c61d3eb6ca35 + postcss: ^8.4.31 + checksum: 308e3fb84c35e4703532de1efa5d6e8444cc5f167d0e40f42d7ea3fa3a37d9d636fd10729847d078e0c303eee16f8548d14b6f88a3fce4e38a2b452648465175 languageName: node linkType: hard -"postcss-discard-empty@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-discard-empty@npm:5.1.1" +"postcss-discard-empty@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-discard-empty@npm:6.0.3" peerDependencies: - postcss: ^8.2.15 - checksum: 970adb12fae5c214c0768236ad9a821552626e77dedbf24a8213d19cc2c4a531a757cd3b8cdd3fc22fb1742471b8692a1db5efe436a71236dec12b1318ee8ff4 + postcss: ^8.4.31 + checksum: bad305572faa066026a295faab37e718cee096589ab827b19c990c55620b2b2a1ce9f0145212651737a66086db01b2676c1927bbb8408c5f9cb42686d5959f00 languageName: node linkType: hard -"postcss-discard-overridden@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-discard-overridden@npm:5.1.0" +"postcss-discard-overridden@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-discard-overridden@npm:6.0.2" peerDependencies: - postcss: ^8.2.15 - checksum: d64d4a545aa2c81b22542895cfcddc787d24119f294d35d29b0599a1c818b3cc51f4ee80b80f5a0a09db282453dd5ac49f104c2117cc09112d0ac9b40b499a41 + postcss: ^8.4.31 + checksum: a38e0fe7a36f83cb9b73c1ba9ee2a48cf93c69ec0ea5753935824ffb71e958e58ae0393171c0f3d0014a397469d09bbb0d56bb5ab80f0280722967e2e273aebb languageName: node linkType: hard -"postcss-discard-unused@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-discard-unused@npm:5.1.0" +"postcss-discard-unused@npm:^6.0.5": + version: 6.0.5 + resolution: "postcss-discard-unused@npm:6.0.5" dependencies: - postcss-selector-parser: ^6.0.5 + postcss-selector-parser: ^6.0.16 peerDependencies: - postcss: ^8.2.15 - checksum: 5c09403a342a065033f5f22cefe6b402c76c2dc0aac31a736a2062d82c2a09f0ff2525b3df3a0c6f4e0ffc7a0392efd44bfe7f9d018e4cae30d15b818b216622 + postcss: ^8.4.31 + checksum: 7962640773240186de38125f142a6555b7f9b2493c4968e0f0b11c6629b2bf43ac70b9fc4ee78aa732d82670ad8bf802b2febc9a9864b022eb68530eded26836 languageName: node linkType: hard @@ -9215,89 +9262,89 @@ __metadata: languageName: node linkType: hard -"postcss-merge-idents@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-merge-idents@npm:5.1.1" +"postcss-merge-idents@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-merge-idents@npm:6.0.3" dependencies: - cssnano-utils: ^3.1.0 + cssnano-utils: ^4.0.2 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: ed8a673617ea6ae3e15d69558063cb1a5eeee01732f78cdc0196ab910324abc30828724ab8dfc4cda27e8c0077542e25688470f829819a2604625a673387ec72 + postcss: ^8.4.31 + checksum: b45780d6d103b8e45a580032747ee6e1842f81863672341a6b4961397e243ca896217bf1f3ee732376a766207d5f610ba8924cf08cf6d5bbd4b093133fd05d70 languageName: node linkType: hard -"postcss-merge-longhand@npm:^5.1.7": - version: 5.1.7 - resolution: "postcss-merge-longhand@npm:5.1.7" +"postcss-merge-longhand@npm:^6.0.5": + version: 6.0.5 + resolution: "postcss-merge-longhand@npm:6.0.5" dependencies: postcss-value-parser: ^4.2.0 - stylehacks: ^5.1.1 + stylehacks: ^6.1.1 peerDependencies: - postcss: ^8.2.15 - checksum: 81c3fc809f001b9b71a940148e242bdd6e2d77713d1bfffa15eb25c1f06f6648d5e57cb21645746d020a2a55ff31e1740d2b27900442913a9d53d8a01fb37e1b + postcss: ^8.4.31 + checksum: 9ae5acf47dc0c1f494684ae55672d55bba7f5ee11c9c0f266aabd7c798e9f7394c6096363cd95685fd21ef088740389121a317772cf523ca22c915009bca2617 languageName: node linkType: hard -"postcss-merge-rules@npm:^5.1.4": - version: 5.1.4 - resolution: "postcss-merge-rules@npm:5.1.4" +"postcss-merge-rules@npm:^6.1.1": + version: 6.1.1 + resolution: "postcss-merge-rules@npm:6.1.1" dependencies: - browserslist: ^4.21.4 + browserslist: ^4.23.0 caniuse-api: ^3.0.0 - cssnano-utils: ^3.1.0 - postcss-selector-parser: ^6.0.5 + cssnano-utils: ^4.0.2 + postcss-selector-parser: ^6.0.16 peerDependencies: - postcss: ^8.2.15 - checksum: 8ab6a569babe6cb412d6612adee74f053cea7edb91fa013398515ab36754b1fec830d68782ed8cdfb44cffdc6b78c79eab157bff650f428aa4460d3f3857447e + postcss: ^8.4.31 + checksum: 43f60a1c88806491cf752ae7871676de0e7a2a9d6d2fc6bc894068cc35a910a63d30f7c7d79545e0926c8b3a9ec583e5e8357203c40b5bad5ff58133b0c900f6 languageName: node linkType: hard -"postcss-minify-font-values@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-minify-font-values@npm:5.1.0" +"postcss-minify-font-values@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-minify-font-values@npm:6.1.0" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 35e858fa41efa05acdeb28f1c76579c409fdc7eabb1744c3bd76e895bb9fea341a016746362a67609688ab2471f587202b9a3e14ea28ad677754d663a2777ece + postcss: ^8.4.31 + checksum: 985e4dd2f89220a4442a822aad7dff016ab58a9dbb7bbca9d01c2d07d5a1e7d8c02e1c6e836abb4c9b4e825b4b80d99ee1f5899e74bf0d969095037738e6e452 languageName: node linkType: hard -"postcss-minify-gradients@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-minify-gradients@npm:5.1.1" +"postcss-minify-gradients@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-minify-gradients@npm:6.0.3" dependencies: - colord: ^2.9.1 - cssnano-utils: ^3.1.0 + colord: ^2.9.3 + cssnano-utils: ^4.0.2 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 27354072a07c5e6dab36731103b94ca2354d4ed3c5bc6aacfdf2ede5a55fa324679d8fee5450800bc50888dbb5e9ed67569c0012040c2be128143d0cebb36d67 + postcss: ^8.4.31 + checksum: 89b95088c3830f829f6d4636d1be4d4f13300bf9f1577c48c25169c81e11ec0026760b9abb32112b95d2c622f09d3b737f4d2975a7842927ccb567e1002ef7b3 languageName: node linkType: hard -"postcss-minify-params@npm:^5.1.4": - version: 5.1.4 - resolution: "postcss-minify-params@npm:5.1.4" +"postcss-minify-params@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-minify-params@npm:6.1.0" dependencies: - browserslist: ^4.21.4 - cssnano-utils: ^3.1.0 + browserslist: ^4.23.0 + cssnano-utils: ^4.0.2 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: bd63e2cc89edcf357bb5c2a16035f6d02ef676b8cede4213b2bddd42626b3d428403849188f95576fc9f03e43ebd73a29bf61d33a581be9a510b13b7f7f100d5 + postcss: ^8.4.31 + checksum: 1e1cc3057d9bcc532c70e40628e96e3aea0081d8072dffe983a270a8cd59c03ac585e57d036b70e43d4ee725f274a05a6a8efac5a715f448284e115c13f82a46 languageName: node linkType: hard -"postcss-minify-selectors@npm:^5.2.1": - version: 5.2.1 - resolution: "postcss-minify-selectors@npm:5.2.1" +"postcss-minify-selectors@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-minify-selectors@npm:6.0.4" dependencies: - postcss-selector-parser: ^6.0.5 + postcss-selector-parser: ^6.0.16 peerDependencies: - postcss: ^8.2.15 - checksum: 6fdbc84f99a60d56b43df8930707da397775e4c36062a106aea2fd2ac81b5e24e584a1892f4baa4469fa495cb87d1422560eaa8f6c9d500f9f0b691a5f95bab5 + postcss: ^8.4.31 + checksum: 150221a84422ca7627c67ee691ee51e0fe2c3583c8108801e9fc93d3be8b538c2eb04fcfdc908270d7eeaeaf01594a20b81311690a873efccb8a23aeafe1c354 languageName: node linkType: hard @@ -9345,192 +9392,191 @@ __metadata: languageName: node linkType: hard -"postcss-normalize-charset@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-normalize-charset@npm:5.1.0" +"postcss-normalize-charset@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-charset@npm:6.0.2" peerDependencies: - postcss: ^8.2.15 - checksum: e79d92971fc05b8b3c9b72f3535a574e077d13c69bef68156a0965f397fdf157de670da72b797f57b0e3bac8f38155b5dd1735ecab143b9cc4032d72138193b4 + postcss: ^8.4.31 + checksum: 5b8aeb17d61578a8656571cd5d5eefa8d4ee7126a99a41fdd322078002a06f2ae96f649197b9c01067a5f3e38a2e4b03e0e3fda5a0ec9e3d7ad056211ce86156 languageName: node linkType: hard -"postcss-normalize-display-values@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-normalize-display-values@npm:5.1.0" +"postcss-normalize-display-values@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-display-values@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: b6eb7b9b02c3bdd62bbc54e01e2b59733d73a1c156905d238e178762962efe0c6f5104544da39f32cade8a4fb40f10ff54b63a8ebfbdff51e8780afb9fbdcf86 + postcss: ^8.4.31 + checksum: da30a9394b0e4a269ccad8d240693a6cd564bcc60e24db67caee00f70ddfbc070ad76faed64c32e6eec9ed02e92565488b7879d4fd6c40d877c290eadbb0bb28 languageName: node linkType: hard -"postcss-normalize-positions@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-normalize-positions@npm:5.1.1" +"postcss-normalize-positions@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-positions@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: d9afc233729c496463c7b1cdd06732469f401deb387484c3a2422125b46ec10b4af794c101f8c023af56f01970b72b535e88373b9058ecccbbf88db81662b3c4 + postcss: ^8.4.31 + checksum: 44fb77583fae4d71b76e38226cf770570876bcf5af6940dc9aeac7a7e2252896b361e0249044766cff8dad445f925378f06a005d6541597573c20e599a62b516 languageName: node linkType: hard -"postcss-normalize-repeat-style@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-normalize-repeat-style@npm:5.1.1" +"postcss-normalize-repeat-style@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-repeat-style@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 2c6ad2b0ae10a1fda156b948c34f78c8f1e185513593de4d7e2480973586675520edfec427645fa168c337b0a6b3ceca26f92b96149741ca98a9806dad30d534 + postcss: ^8.4.31 + checksum: bebdac63bec6777ead3e265fc12527b261cf8d0da1b7f0abb12bda86fd53b7058e4afe392210ac74dac012e413bb1c2a46a1138c89f82b8bf70b81711f620f8c languageName: node linkType: hard -"postcss-normalize-string@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-normalize-string@npm:5.1.0" +"postcss-normalize-string@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-string@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 6e549c6e5b2831e34c7bdd46d8419e2278f6af1d5eef6d26884a37c162844e60339340c57e5e06058cdbe32f27fc6258eef233e811ed2f71168ef2229c236ada + postcss: ^8.4.31 + checksum: 5e8e253c528b542accafc142846fb33643c342a787c86e5b68c6287c7d8f63c5ae7d4d3fc28e3daf80821cc26a91add135e58bdd62ff9c735fca65d994898c7d languageName: node linkType: hard -"postcss-normalize-timing-functions@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-normalize-timing-functions@npm:5.1.0" +"postcss-normalize-timing-functions@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-timing-functions@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: da550f50e90b0b23e17b67449a7d1efd1aa68288e66d4aa7614ca6f5cc012896be1972b7168eee673d27da36504faccf7b9f835c0f7e81243f966a42c8c030aa + postcss: ^8.4.31 + checksum: 1970f5aad04be11f99d51c59e27debb6fd7b49d0fa4a8879062b42c82113f8e520a284448727add3b54de85deefb8bd5fe554f618406586e9ad8fc9d060609f1 languageName: node linkType: hard -"postcss-normalize-unicode@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-normalize-unicode@npm:5.1.1" +"postcss-normalize-unicode@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-normalize-unicode@npm:6.1.0" dependencies: - browserslist: ^4.21.4 + browserslist: ^4.23.0 postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 4c24d26cc9f4b19a9397db4e71dd600dab690f1de8e14a3809e2aa1452dbc3791c208c38a6316bbc142f29e934fdf02858e68c94038c06174d78a4937e0f273c + postcss: ^8.4.31 + checksum: 69ef35d06242061f0c504c128b83752e0f8daa30ebb26734de7d090460910be0b2efd8b17b1d64c3c85b95831a041faad9ad0aaba80e239406a79cfad3d63568 languageName: node linkType: hard -"postcss-normalize-url@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-normalize-url@npm:5.1.0" - dependencies: - normalize-url: ^6.0.1 - postcss-value-parser: ^4.2.0 - peerDependencies: - postcss: ^8.2.15 - checksum: 3bd4b3246d6600230bc827d1760b24cb3101827ec97570e3016cbe04dc0dd28f4dbe763245d1b9d476e182c843008fbea80823061f1d2219b96f0d5c724a24c0 - languageName: node - linkType: hard - -"postcss-normalize-whitespace@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-normalize-whitespace@npm:5.1.1" +"postcss-normalize-url@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-url@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 12d8fb6d1c1cba208cc08c1830959b7d7ad447c3f5581873f7e185f99a9a4230c43d3af21ca12c818e4690a5085a95b01635b762ad4a7bef69d642609b4c0e19 + postcss: ^8.4.31 + checksum: bef51a18bbfee4fbf0381fec3c91e6c0dace36fca053bbd5f228e653d2732b6df3985525d79c4f7fc89f840ed07eb6d226e9d7503ecdc6f16d6d80cacae9df33 languageName: node linkType: hard -"postcss-ordered-values@npm:^5.1.3": - version: 5.1.3 - resolution: "postcss-ordered-values@npm:5.1.3" - dependencies: - cssnano-utils: ^3.1.0 - postcss-value-parser: ^4.2.0 - peerDependencies: - postcss: ^8.2.15 - checksum: 6f3ca85b6ceffc68aadaf319d9ee4c5ac16d93195bf8cba2d1559b631555ad61941461cda6d3909faab86e52389846b2b36345cff8f0c3f4eb345b1b8efadcf9 - languageName: node - linkType: hard - -"postcss-reduce-idents@npm:^5.2.0": - version: 5.2.0 - resolution: "postcss-reduce-idents@npm:5.2.0" +"postcss-normalize-whitespace@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-normalize-whitespace@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: f0d644c86e160dd36ee4dd924ab7d6feacac867c87702e2f98f96b409430a62de4fec2dfc3c8731bda4e14196e29a752b4558942f0af2a3e6cd7f1f4b173db8e + postcss: ^8.4.31 + checksum: 6081eb3a4b305749eec02c00a95c2d236336a77ee636bb1d939f18d5dfa5ba82b7cf7fa072e83f9133d0bc984276596af3fe468bdd67c742ce69e9c63dbc218d languageName: node linkType: hard -"postcss-reduce-initial@npm:^5.1.2": - version: 5.1.2 - resolution: "postcss-reduce-initial@npm:5.1.2" +"postcss-ordered-values@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-ordered-values@npm:6.0.2" dependencies: - browserslist: ^4.21.4 + cssnano-utils: ^4.0.2 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.4.31 + checksum: c3d96177b4ffa43754e835e30c40043cc75ab1e95eb6c55ac8723eb48c13a12e986250e63d96619bbbd1a098876a1c0c1b3b7a8e1de1108a009cf7aa0beac834 + languageName: node + linkType: hard + +"postcss-reduce-idents@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-reduce-idents@npm:6.0.3" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.4.31 + checksum: 1feff316838f947386c908f50807cf1b9589fd09b8e8df633a01f2640af5492833cc892448938ceba10ab96826c44767b8f2e1569d587579423f2db81202f7c7 + languageName: node + linkType: hard + +"postcss-reduce-initial@npm:^6.1.0": + version: 6.1.0 + resolution: "postcss-reduce-initial@npm:6.1.0" + dependencies: + browserslist: ^4.23.0 caniuse-api: ^3.0.0 peerDependencies: - postcss: ^8.2.15 - checksum: 55db697f85231a81f1969d54c894e4773912d9ddb914f9b03d2e73abc4030f2e3bef4d7465756d0c1acfcc2c2d69974bfb50a972ab27546a7d68b5a4fc90282b + postcss: ^8.4.31 + checksum: 39e4034ffbf62a041b66944c5cebc4b17f656e76b97568f7f6230b0b886479e5c75b02ae4ba48c472cb0bde47489f9ed1fe6110ae8cff0d7b7165f53c2d64a12 languageName: node linkType: hard -"postcss-reduce-transforms@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-reduce-transforms@npm:5.1.0" +"postcss-reduce-transforms@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-reduce-transforms@npm:6.0.2" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: 0c6af2cba20e3ff63eb9ad045e634ddfb9c3e5c0e614c020db2a02f3aa20632318c4ede9e0c995f9225d9a101e673de91c0a6e10bb2fa5da6d6c75d15a55882f + postcss: ^8.4.31 + checksum: c424cc554eb5d253b7687b64925a13fc16759f058795d223854f5a20d9bca641b5f25d0559d03287e63f07a4629c24ac78156adcf604483fcad3c51721da0a08 languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9": - version: 6.0.11 - resolution: "postcss-selector-parser@npm:6.0.11" +"postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.16, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" dependencies: cssesc: ^3.0.0 util-deprecate: ^1.0.2 - checksum: 0b01aa9c2d2c8dbeb51e9b204796b678284be9823abc8d6d40a8b16d4149514e922c264a8ed4deb4d6dbced564b9be390f5942c058582d8656351516d6c49cde + checksum: ce9440fc42a5419d103f4c7c1847cb75488f3ac9cbe81093b408ee9701193a509f664b4d10a2b4d82c694ee7495e022f8f482d254f92b7ffd9ed9dea696c6f84 languageName: node linkType: hard -"postcss-sort-media-queries@npm:^4.4.1": - version: 4.4.1 - resolution: "postcss-sort-media-queries@npm:4.4.1" +"postcss-sort-media-queries@npm:^5.2.0": + version: 5.2.0 + resolution: "postcss-sort-media-queries@npm:5.2.0" dependencies: - sort-css-media-queries: 2.1.0 + sort-css-media-queries: 2.2.0 peerDependencies: - postcss: ^8.4.16 - checksum: 70b42e479bb1d15d8628678eefefd547d309e33e64262fe437630fe62d8e4b3adcae7f2b48ef8da9d3173576d4af109a9ffa9514573db1281deef324f5ea166f + postcss: ^8.4.23 + checksum: d4a976a64b53234762cc35c06ce97c1684bd7a64ead17e84c2047676c7307945be7c005235e6aac7c4620e1f835d6ba1a7dcf018ab7fe0a47657c62c96ad9f35 languageName: node linkType: hard -"postcss-svgo@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-svgo@npm:5.1.0" +"postcss-svgo@npm:^6.0.3": + version: 6.0.3 + resolution: "postcss-svgo@npm:6.0.3" dependencies: postcss-value-parser: ^4.2.0 - svgo: ^2.7.0 + svgo: ^3.2.0 peerDependencies: - postcss: ^8.2.15 - checksum: d86eb5213d9f700cf5efe3073799b485fb7cacae0c731db3d7749c9c2b1c9bc85e95e0baeca439d699ff32ea24815fc916c4071b08f67ed8219df229ce1129bd + postcss: ^8.4.31 + checksum: 1a7d1c8dea555884a7791e28ec2c22ea92331731067584ff5a23042a0e615f88fefde04e1140f11c262a728ef9fab6851423b40b9c47f9ae05353bd3c0ff051a languageName: node linkType: hard -"postcss-unique-selectors@npm:^5.1.1": - version: 5.1.1 - resolution: "postcss-unique-selectors@npm:5.1.1" +"postcss-unique-selectors@npm:^6.0.4": + version: 6.0.4 + resolution: "postcss-unique-selectors@npm:6.0.4" dependencies: - postcss-selector-parser: ^6.0.5 + postcss-selector-parser: ^6.0.16 peerDependencies: - postcss: ^8.2.15 - checksum: 637e7b786e8558265775c30400c54b6b3b24d4748923f4a39f16a65fd0e394f564ccc9f0a1d3c0e770618a7637a7502ea1d0d79f731d429cb202255253c23278 + postcss: ^8.4.31 + checksum: b09df9943b4e858e88b30f3d279ce867a0490df806f1f947d286b0a4e95ba923f1229c385e5bf365f4f124f1edccda41ec18ccad4ba8798d829279d6dc971203 languageName: node linkType: hard @@ -9541,23 +9587,23 @@ __metadata: languageName: node linkType: hard -"postcss-zindex@npm:^5.1.0": - version: 5.1.0 - resolution: "postcss-zindex@npm:5.1.0" +"postcss-zindex@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-zindex@npm:6.0.2" peerDependencies: - postcss: ^8.2.15 - checksum: 8581e0ee552622489dcb9fb9609a3ccc261a67a229ba91a70bd138fe102a2d04cedb14642b82b673d4cac7b559ef32574f2dafde2ff7816eecac024d231c5ead + postcss: ^8.4.31 + checksum: 394119e47b0fb098dc53d1bcf71b5500ab29605fe106526b2e81290bff179174ee00a82a4d4be5a42d4ef4138e8a3d6aabeef3b06cf7cb15b851848c8585d53b languageName: node linkType: hard -"postcss@npm:^8.4.17, postcss@npm:^8.4.21, postcss@npm:^8.4.26": - version: 8.4.31 - resolution: "postcss@npm:8.4.31" +"postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.26, postcss@npm:^8.4.38": + version: 8.4.41 + resolution: "postcss@npm:8.4.41" dependencies: - nanoid: ^3.3.6 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea + nanoid: ^3.3.7 + picocolors: ^1.0.1 + source-map-js: ^1.2.0 + checksum: f865894929eb0f7fc2263811cc853c13b1c75103028b3f4f26df777e27b201f1abe21cb4aa4c2e901c80a04f6fb325ee22979688fe55a70e2ea82b0a517d3b6f languageName: node linkType: hard @@ -9812,14 +9858,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 @@ -9881,6 +9927,17 @@ __metadata: languageName: node linkType: hard +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": + version: 6.0.0 + resolution: "@docusaurus/react-loadable@npm:6.0.0" + dependencies: + "@types/react": "*" + peerDependencies: + react: "*" + checksum: 4c32061b2fc10689d5d8ba11ead71b69e4c8a55fcfeafb551a6747b1a7b496c4f2d8dbb5d023f5cafc2a9aea9d14582bdb324d11e6f9b8c3049d45b74439203f + languageName: node + linkType: hard + "react-router-config@npm:^5.1.1": version: 5.1.1 resolution: "react-router-config@npm:5.1.1" @@ -9930,11 +9987,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 @@ -10387,15 +10444,15 @@ __metadata: linkType: hard "sass@npm:^1.57.1": - version: 1.75.0 - resolution: "sass@npm:1.75.0" + version: 1.77.8 + resolution: "sass@npm:1.77.8" dependencies: chokidar: ">=3.0.0 <4.0.0" immutable: ^4.0.0 source-map-js: ">=0.6.2 <2.0.0" bin: sass: sass.js - checksum: bfb9f5ddb6a2e1fe0c1ba6191cdb17afa7b40c1eb892c7152f6a29ff2b06dc7a510bdb648f8cca0179dcb3965920ebeb8894f0710b0b450a99db563831345033 + checksum: 6b5dce17faa1bd1e349b4825bf7f76559a32f3f95d789cd2847623c88ee9635e1485d3458532a05fa5b9134cfbce79a4bad3f13dc63c2433632347674db0abae languageName: node linkType: hard @@ -10406,12 +10463,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 @@ -10437,15 +10494,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0": - version: 4.0.0 - resolution: "schema-utils@npm:4.0.0" +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.0.1": + version: 4.2.0 + resolution: "schema-utils@npm:4.2.0" dependencies: "@types/json-schema": ^7.0.9 - ajv: ^8.8.0 + ajv: ^8.9.0 ajv-formats: ^2.1.1 - ajv-keywords: ^5.0.0 - checksum: c843e92fdd1a5c145dbb6ffdae33e501867f9703afac67bdf35a685e49f85b1dcc10ea250033175a64bd9d31f0555bc6785b8359da0c90bcea30cf6dfbb55a8f + ajv-keywords: ^5.1.0 + checksum: 26a0463d47683258106e6652e9aeb0823bf0b85843039e068b57da1892f7ae6b6b1094d48e9ed5ba5cbe9f7166469d880858b9d91abe8bd249421eb813850cde languageName: node linkType: hard @@ -10730,6 +10787,16 @@ __metadata: languageName: node linkType: hard +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "sockjs@npm:^0.3.24": version: 0.3.24 resolution: "sockjs@npm:0.3.24" @@ -10762,17 +10829,17 @@ __metadata: languageName: node linkType: hard -"sort-css-media-queries@npm:2.1.0": - version: 2.1.0 - resolution: "sort-css-media-queries@npm:2.1.0" - checksum: 25cb8f08b148a2ed83d0bc1cf20ddb888d3dee2a3c986896099a21b28b999d5cca3e46a9ef64381bb36fca0fc820471713f2e8af2729ecc6e108ab2b3b315ea9 +"sort-css-media-queries@npm:2.2.0": + version: 2.2.0 + resolution: "sort-css-media-queries@npm:2.2.0" + checksum: c090c9a27be40f3e50f5f9bc9d85a8af0e2c5152565eca34bdb028d952749bce169bc5abef21a5a385ca6221a0869640c9faf58f082ac46de9085ebdb506291f languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2": - version: 1.0.2 - resolution: "source-map-js@npm:1.0.2" - checksum: c049a7fc4deb9a7e9b481ae3d424cc793cb4845daa690bc5a05d428bf41bf231ced49b4cf0c9e77f9d42fdb3d20d6187619fc586605f5eabe995a316da8d377c +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 791a43306d9223792e84293b00458bf102a8946e7188f3db0e4e22d8d530b5f80a4ce468eb5ec0bf585443ad55ebbd630bf379c98db0b1f317fd902500217f97 languageName: node linkType: hard @@ -10786,7 +10853,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0": +"source-map@npm:^0.6.0, source-map@npm:~0.6.0": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 @@ -10857,13 +10924,6 @@ __metadata: languageName: node linkType: hard -"stable@npm:^0.1.8": - version: 0.1.8 - resolution: "stable@npm:0.1.8" - checksum: 2ff482bb100285d16dd75cd8f7c60ab652570e8952c0bfa91828a2b5f646a0ff533f14596ea4eabd48bb7f4aeea408dce8f8515812b975d958a4cc4fa6b9dfeb - languageName: node - linkType: hard - "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -11010,15 +11070,15 @@ __metadata: languageName: node linkType: hard -"stylehacks@npm:^5.1.1": - version: 5.1.1 - resolution: "stylehacks@npm:5.1.1" +"stylehacks@npm:^6.1.1": + version: 6.1.1 + resolution: "stylehacks@npm:6.1.1" dependencies: - browserslist: ^4.21.4 - postcss-selector-parser: ^6.0.4 + browserslist: ^4.23.0 + postcss-selector-parser: ^6.0.16 peerDependencies: - postcss: ^8.2.15 - checksum: 11175366ef52de65bf06cefba0ddc9db286dc3a1451fd2989e74c6ea47091a02329a4bf6ce10b1a36950056927b6bbbe47c5ab3a1f4c7032df932d010fbde5a2 + postcss: ^8.4.31 + checksum: 7bef69822280a23817caa43969de76d77ba34042e9f1f7baaeda8f22b1d8c20f1f839ad028552c169e158e387830f176feccd0324b07ef6ec657cba1dd0b2466 languageName: node linkType: hard @@ -11063,20 +11123,20 @@ __metadata: languageName: node linkType: hard -"svgo@npm:^2.7.0, svgo@npm:^2.8.0": - version: 2.8.0 - resolution: "svgo@npm:2.8.0" +"svgo@npm:^3.0.2, svgo@npm:^3.2.0": + version: 3.3.2 + resolution: "svgo@npm:3.3.2" dependencies: "@trysound/sax": 0.2.0 commander: ^7.2.0 - css-select: ^4.1.3 - css-tree: ^1.1.3 - csso: ^4.2.0 + css-select: ^5.1.0 + css-tree: ^2.3.1 + css-what: ^6.1.0 + csso: ^5.0.5 picocolors: ^1.0.0 - stable: ^0.1.8 bin: - svgo: bin/svgo - checksum: b92f71a8541468ffd0b81b8cdb36b1e242eea320bf3c1a9b2c8809945853e9d8c80c19744267eb91cabf06ae9d5fff3592d677df85a31be4ed59ff78534fa420 + svgo: ./bin/svgo + checksum: a3f8aad597dec13ab24e679c4c218147048dc1414fe04e99447c5f42a6e077b33d712d306df84674b5253b98c9b84dfbfb41fdd08552443b04946e43d03e054e languageName: node linkType: hard @@ -11449,17 +11509,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.13": - version: 1.0.13 - resolution: "update-browserslist-db@npm:1.0.13" +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" dependencies: - escalade: ^3.1.1 - picocolors: ^1.0.0 + escalade: ^3.1.2 + picocolors: ^1.0.1 peerDependencies: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 1e47d80182ab6e4ad35396ad8b61008ae2a1330221175d0abd37689658bdb61af9b705bfc41057fd16682474d79944fb2d86767c5ed5ae34b6276b9bed353322 + checksum: 7b74694d96f0c360f01b702e72353dc5a49df4fe6663d3ee4e5c628f061576cddf56af35a3a886238c01dd3d8f231b7a86a8ceaa31e7a9220ae31c1c1238e562 languageName: node linkType: hard @@ -11944,7 +12004,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": +"yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" checksum: ce4ada136e8a78a0b08dc10b4b900936912d15de59905b2bf415b4d33c63df1d555d23acb2a41b23cf9fb5da41c256441afca3d6509de7247daa062fd2c5ea5f diff --git a/mkdocs.yml b/mkdocs.yml index 3b0c7d9caf..4d4d039756 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,7 @@ nav: - 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' @@ -137,7 +138,6 @@ nav: - Proxying: 'plugins/proxying.md' - Backend plugin: 'plugins/backend-plugin.md' - Call existing API: 'plugins/call-existing-api.md' - - URL Reader: 'plugins/url-reader.md' - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: @@ -182,6 +182,7 @@ nav: - Local Development: - Linking in Local Packages: 'tooling/local-dev/linking-local-packages.md' - Debugging Backstage: 'tooling/local-dev/debugging.md' + - Profiling Backstage: 'tooling/local-dev/profiling.md' - Package Metadata: 'tooling/package-metadata.md' - Deployment: - Deploying Backstage: 'deployment/index.md' diff --git a/package.json b/package.json index d9b384fb7d..4028f7bacd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.30.0-next.3", + "version": "1.31.0-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 410841fec9..449f249236 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/app-defaults +## 1.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @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 + +### 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 diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 284e832ae5..0e0cb11cbe 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.10-next.2", + "version": "1.5.11-next.0", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 62a7b18df0..68879cd7aa 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,37 @@ # app-next-example-plugin +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + +## 0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + +## 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 diff --git a/packages/app-next-example-plugin/api-report.md b/packages/app-next-example-plugin/api-report.md index 962b1473a8..06d325b844 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -3,11 +3,46 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } 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: FrontendPlugin< + {}, + {}, + { + 'page:example': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + } +>; export default examplePlugin; // @public (undocumented) diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index ad179ebbe5..89e2d353f6 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.14-next.2", + "version": "0.0.15-next.1", "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 6382efa811..8ddcaad4b3 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,180 @@ # example-app-next +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.1 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + - @backstage/plugin-signals@0.0.10-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.9-next.1 + - @backstage/plugin-app-visualizer@0.1.10-next.1 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-import@0.12.3-next.1 + - @backstage/plugin-home@0.7.10-next.1 + - @backstage/plugin-kubernetes@0.11.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + - @backstage/plugin-notifications@0.3.1-next.1 + - @backstage/plugin-org@0.6.29-next.1 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder@1.25.0-next.1 + - @backstage/plugin-search@1.4.16-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + - @backstage/plugin-user-settings@0.8.12-next.1 + +## 0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-import@0.12.3-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-user-settings@0.8.12-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-kubernetes@0.11.14-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.0 + - @backstage/plugin-api-docs@0.11.9-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/plugin-search@1.4.16-next.0 + - @backstage/plugin-home@0.7.10-next.0 + - @backstage/plugin-org@0.6.29-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/plugin-signals@0.0.10-next.0 + - @backstage/plugin-app-visualizer@0.1.10-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-notifications@0.3.1-next.0 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-search-common@1.2.14 + +## 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 diff --git a/packages/app-next/package.json b/packages/app-next/package.json index a12371a135..397be2197a 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.14-next.3", + "version": "0.0.15-next.1", "private": true, "repository": { "type": "git", @@ -21,9 +21,11 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-app-api": "workspace:^", + "@backstage/frontend-defaults": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration-react": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", + "@backstage/plugin-app": "workspace:^", "@backstage/plugin-app-visualizer": "workspace:^", "@backstage/plugin-auth-react": "workspace:^", "@backstage/plugin-catalog": "workspace:^", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index b57c680474..0d686cd5a5 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -26,10 +26,15 @@ import homePlugin, { import { coreExtensionData, createExtension, - createApiExtension, - createExtensionOverrides, + ApiBlueprint, + createFrontendModule, } 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'; @@ -43,7 +48,10 @@ import { scmIntegrationsApiRef, } from '@backstage/integration-react'; import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; -import { signInPageOverrides } from './overrides/SignInPage'; +import { signInPageModule } 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'; /* @@ -74,28 +82,62 @@ TODO: /* app.tsx */ -const homePageExtension = createExtension({ - name: 'myhomepage', - attachTo: { id: 'page:home', input: 'props' }, - output: { - children: coreExtensionData.reactElement, - title: titleExtensionDataRef, - }, - factory() { - return { children: homePage, title: 'just a title' }; - }, +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 scmAuthExtension = createApiExtension({ - factory: ScmAuth.createDefaultApiFactory(), +const customHomePageModule = createFrontendModule({ + pluginId: 'home', + extensions: [ + createExtension({ + name: 'my-home-page', + attachTo: { id: 'page:home', input: 'props' }, + output: [coreExtensionData.reactElement, titleExtensionDataRef], + factory() { + return [ + coreExtensionData.reactElement(homePage), + titleExtensionDataRef('just a title'), + ]; + }, + }), + ], }); -const scmIntegrationApi = createApiExtension({ - factory: createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), +const scmModule = createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + name: 'scm-auth', + params: { + factory: ScmAuth.createDefaultApiFactory(), + }, + }), + ApiBlueprint.make({ + name: 'scm-integrations', + params: { + factory: createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + }, + }), + ], +}); + +const notFoundErrorPageModule = createFrontendModule({ + pluginId: 'app', + extensions: [notFoundErrorPage], }); const collectedLegacyPlugins = convertLegacyApp( @@ -107,21 +149,16 @@ const collectedLegacyPlugins = convertLegacyApp( const app = createApp({ features: [ pagesPlugin, - techdocsPlugin, + convertedTechdocsPlugin, userSettingsPlugin, homePlugin, appVisualizerPlugin, kubernetesPlugin, - signInPageOverrides, + signInPageModule, + scmModule, + notFoundErrorPageModule, + customHomePageModule, ...collectedLegacyPlugins, - createExtensionOverrides({ - extensions: [ - homePageExtension, - scmAuthExtension, - scmIntegrationApi, - notFoundErrorPage, - ], - }), ], /* Handled through config instead */ // bindRoutes({ bind }) { diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx index f5108c6c15..458e716910 100644 --- a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx +++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx @@ -52,6 +52,7 @@ export function CustomNotFoundErrorPage() { } export default createComponentExtension({ + name: 'not-found-error-page', ref: coreComponentRefs.notFoundErrorPage, loader: { sync: () => CustomNotFoundErrorPage }, }); diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index 095c992e18..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,95 +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 - {page1Link && ( + params: { + defaultPath: '/', + routeRef: indexRouteRef, + loader: async () => { + const Component = () => { + const page1Link = useRouteRef(page1RouteRef); + return ( +
+ op + {page1Link && ( +
+ Page 1 +
+ )}
- Page 1 + Home +
+
+ GraphiQL +
+
+ Search +
+
+ Settings
- )} -
- Home
-
- GraphiQL -
-
- Search -
-
- Settings -
-
- ); - }; - return ; + ); + }; + return ; + }, }, }); -const Page1 = createPageExtension({ +const Page1 = PageBlueprint.make({ 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

- {indexLink && Go back} - Page 2 - {/* Page 2 */} - {xLink && Page X} + params: { + defaultPath: '/page1', + routeRef: page1RouteRef, + loader: async () => { + const Component = () => { + const indexLink = useRouteRef(indexRouteRef); + const xLink = useRouteRef(externalPageXRouteRef); + // const page2Link = useRouteRef(page2RouteRef); + return (
- Sub-page content: +

This is page 1

+ {indexLink && Go back} + Page 2 + {/* Page 2 */} + {xLink && Page X} +
- - This is also page 1} /> - This is page 2} /> - + Sub-page content: +
+ + This is also page 1} /> + This is page 2} /> + +
-
- ); - }; - return ; + ); + }; + return ; + }, }, }); -const ExternalPage = createPageExtension({ +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

- {indexLink && 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 index 56bc698ffd..f258a660e3 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/app-next/src/index-public-experimental.tsx @@ -14,36 +14,12 @@ * 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'; +import { signInPageModule } from './overrides/SignInPage'; +import { createPublicSignInApp } from '@backstage/frontend-defaults'; -const authRedirectExtension = createExtension({ - namespace: 'app', - name: 'layout', - attachTo: { id: 'app/root', input: 'children' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ - element: , - }), -}); - -const app = createApp({ - features: [ - signInPageOverrides, - createExtensionOverrides({ - extensions: [authRedirectExtension], - }), - ], +const app = createPublicSignInApp({ + features: [signInPageModule], }); ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/packages/app-next/src/overrides/SignInPage.tsx b/packages/app-next/src/overrides/SignInPage.tsx index 364a749293..f1e37a241b 100644 --- a/packages/app-next/src/overrides/SignInPage.tsx +++ b/packages/app-next/src/overrides/SignInPage.tsx @@ -17,15 +17,19 @@ import React from 'react'; import { SignInPage } from '@backstage/core-components'; import { - createExtensionOverrides, - createSignInPageExtension, + SignInPageBlueprint, + createFrontendModule, } from '@backstage/frontend-plugin-api'; -const signInPage = createSignInPageExtension({ +const signInPage = SignInPageBlueprint.make({ name: 'guest', - loader: async () => props => , + params: { + loader: async () => props => + , + }, }); -export const signInPageOverrides = createExtensionOverrides({ +export const signInPageModule = createFrontendModule({ + pluginId: 'app', extensions: [signInPage], }); diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 88e1134800..4cfd9ca1a8 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,169 @@ # example-app +## 0.2.101-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.1 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + - @backstage/plugin-signals@0.0.10-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.9-next.1 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-import@0.12.3-next.1 + - @backstage/plugin-devtools@0.1.18-next.1 + - @backstage/plugin-home@0.7.10-next.1 + - @backstage/plugin-kubernetes@0.11.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + - @backstage/plugin-notifications@0.3.1-next.1 + - @backstage/plugin-org@0.6.29-next.1 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder@1.25.0-next.1 + - @backstage/plugin-search@1.4.16-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + - @backstage/plugin-user-settings@0.8.12-next.1 + +## 0.2.101-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/plugin-catalog-import@0.12.3-next.0 + - @backstage/plugin-catalog-graph@0.4.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-user-settings@0.8.12-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/plugin-kubernetes@0.11.14-next.0 + - @backstage/plugin-scaffolder@1.25.0-next.0 + - @backstage/plugin-api-docs@0.11.9-next.0 + - @backstage/plugin-devtools@0.1.18-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/plugin-search@1.4.16-next.0 + - @backstage/plugin-home@0.7.10-next.0 + - @backstage/plugin-org@0.6.29-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/plugin-signals@0.0.10-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.15-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-notifications@0.3.1-next.0 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-search-common@1.2.14 + +## 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 diff --git a/packages/app/package.json b/packages/app/package.json index c4c0c18391..9d9431b6c1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.100-next.3", + "version": "0.2.101-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index d741dea098..ba556e8616 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -73,8 +73,7 @@ import { DelayingComponentFieldExtension } from './components/scaffolder/customS import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate'; import { searchPage } from './components/search/SearchPage'; import { providers } from './identityProviders'; -import * as plugins from './plugins'; - +import { SignalsDisplay } from '@backstage/plugin-signals'; import { techDocsPage } from './components/techdocs/TechDocsPage'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; @@ -86,7 +85,6 @@ import { NotificationsPage } from '@backstage/plugin-notifications'; const app = createApp({ apis, - plugins: Object.values(plugins), icons: { // Custom icon example alert: AlarmIcon, @@ -217,6 +215,7 @@ export default app.createRoot( <> + {routes} diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts deleted file mode 100644 index 9280536997..0000000000 --- a/packages/app/src/plugins.ts +++ /dev/null @@ -1,20 +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. - */ - -// TODO(Rugvip): This plugin is currently not part of the app element tree, -// ideally we have an API for the context menu that permits that. -export { homePlugin } from '@backstage/plugin-home'; -export { signalsPlugin } from '@backstage/plugin-signals'; diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index c7c8883302..9605e30ba3 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/backend-app-api +## 0.10.0-next.1 + +### Patch Changes + +- c246372: Updated the error message for missing service dependencies to include the plugin and module IDs. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.10.0-next.0 + +### Minor Changes + +- 19ff127: **BREAKING**: The deprecated `identityServiceFactory` and `tokenManagerServiceFactory` have been removed. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- cd38da8: Deprecate the `featureDiscoveryServiceFactory` in favor of using `@backstage/backend-defaults#discoveryFeatureLoader` instead. +- 7f779c7: `auth.externalAccess` should be optional in the config schema +- 51a69b5: Fix feature loaders in CJS double-default nested builds +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 diff --git a/packages/backend-app-api/api-report-alpha.md b/packages/backend-app-api/api-report-alpha.md index 8616eec29a..163cc325a1 100644 --- a/packages/backend-app-api/api-report-alpha.md +++ b/packages/backend-app-api/api-report-alpha.md @@ -4,14 +4,13 @@ ```ts import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; -// @alpha (undocumented) -export const featureDiscoveryServiceFactory: ServiceFactoryCompat< +// @alpha @deprecated (undocumented) +export const featureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, 'root', - 'singleton', - undefined + '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 dea309ebb0..88fa34e6e4 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -3,55 +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 { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; -import { TokenManagerService } from '@backstage/backend-plugin-api'; -import { transport } from 'winston'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { UserInfoService } from '@backstage/backend-plugin-api'; - -// @public @deprecated (undocumented) -export const authServiceFactory: ServiceFactoryCompat< - AuthService, - 'plugin', - 'singleton', - undefined ->; // @public (undocumented) export interface Backend { @@ -63,43 +16,12 @@ export interface Backend { default: BackendFeature; }>, ): void; - // @deprecated (undocumented) - add( - feature: - | (() => BackendFeature) - | Promise<{ - default: () => BackendFeature; - }>, - ): void; // (undocumented) start(): Promise; // (undocumented) stop(): Promise; } -// @public @deprecated (undocumented) -export const cacheServiceFactory: ServiceFactoryCompat< - CacheService, - 'plugin', - 'singleton', - undefined ->; - -// 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, @@ -110,276 +32,4 @@ export interface CreateSpecializedBackendOptions { // (undocumented) defaultServiceFactories: ServiceFactory[]; } - -// @public @deprecated (undocumented) -export const databaseServiceFactory: ServiceFactoryCompat< - DatabaseService, - 'plugin', - 'singleton', - undefined ->; - -// @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: ServiceFactoryCompat< - DiscoveryService, - 'plugin', - 'singleton', - undefined ->; - -// 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: ServiceFactoryCompat< - HttpAuthService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated -export const httpRouterServiceFactory: ServiceFactoryCompat< - HttpRouterService, - 'plugin', - 'singleton', - undefined ->; - -// 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: ServiceFactoryCompat< - IdentityService, - 'plugin', - 'singleton', - IdentityFactoryOptions ->; - -// 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: ServiceFactoryCompat< - LifecycleService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated -export function loadBackendConfig(options: { - remote?: LoadConfigOptionsRemote; - argv: string[]; - additionalConfigs?: AppConfig[]; - watch?: boolean; -}): Promise<{ - config: Config; -}>; - -// @public @deprecated -export const loggerServiceFactory: ServiceFactoryCompat< - LoggerService, - 'plugin', - 'singleton', - undefined ->; - -// @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: ServiceFactoryCompat< - PermissionsService, - 'plugin', - 'singleton', - undefined ->; - -// 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: ServiceFactoryCompat< - RootConfigService, - 'root', - 'singleton', - RootConfigFactoryOptions ->; - -// 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) & - ServiceFactory; - -// @public @deprecated -export const rootLifecycleServiceFactory: ServiceFactoryCompat< - RootLifecycleService, - 'root', - 'singleton', - undefined ->; - -// @public @deprecated -export const rootLoggerServiceFactory: ServiceFactoryCompat< - RootLoggerService, - 'root', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const schedulerServiceFactory: ServiceFactoryCompat< - SchedulerService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const tokenManagerServiceFactory: ServiceFactoryCompat< - TokenManagerService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const urlReaderServiceFactory: ServiceFactoryCompat< - UrlReaderService, - 'plugin', - 'singleton', - undefined ->; - -// @public @deprecated (undocumented) -export const userInfoServiceFactory: ServiceFactoryCompat< - UserInfoService, - 'plugin', - 'singleton', - undefined ->; - -// @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 e94da62b77..b5e56af67c 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -16,297 +16,12 @@ export interface Config { backend?: { - auth?: { - /** - * This disables the otherwise default auth policy, which requires all - * requests to be authenticated with either user or service credentials. - * - * Disabling this check means that the backend will no longer block - * unauthenticated requests, but instead allow them to pass through to - * plugins. - * - * If permissions are enabled, unauthenticated requests will be treated - * exactly as such, leaving it to the permission policy to determine what - * permissions should be allowed for an unauthenticated identity. Note - * that this will also apply to service-to-service calls between plugins - * unless you configure credentials for service calls. - */ - dangerouslyDisableDefaultAuthPolicy?: boolean; - - /** Controls how to store keys for plugin-to-plugin auth */ - pluginKeyStore?: - | { type: 'database' } - | { - type: 'static'; - static: { - /** - * Must be declared at least once and the first one will be used for signing. - */ - keys: Array<{ - /** - * Path to the public key file in the SPKI format. Should be an absolute path. - */ - publicKeyFile: string; - /** - * Path to the matching private key file in the PKCS#8 format. Should be an absolute path. - * - * The first array entry must specify a private key file, the rest must not. - */ - privateKeyFile?: string; - /** - * ID to uniquely identify this key within the JWK set. - */ - keyId: string; - /** - * JWS "alg" (Algorithm) Header Parameter value. Defaults to ES256. - * Must match the algorithm used to generate the keys in the provided files - */ - algorithm?: string; - }>; - }; - }; - - /** - * Configures methods of external access, ie ways for callers outside of - * the Backstage ecosystem to get authorized for access to APIs that do - * not permit unauthorized access. - */ - externalAccess: Array< - | { - /** - * This is the legacy service-to-service access method, where a set - * of static keys were shared among plugins and used for symmetric - * signing and verification. These correspond to the old - * `backend.auth.keys` set and retain their behavior for backwards - * compatibility. Please migrate to other access methods when - * possible. - * - * Callers generate JWT tokens with the following payload: - * - * ```json - * { - * "sub": "backstage-plugin", - * "exp": - * } - * ``` - * - * And sign them with HS256, using the base64 decoded secret. The - * tokens are then passed along with requests in the Authorization - * header: - * - * ``` - * Authorization: Bearer eyJhbGciOiJIUzI... - * ``` - */ - type: 'legacy'; - options: { - /** - * Any set of base64 encoded random bytes to be used as both the - * signing and verification key. Should be sufficiently long so as - * not to be easy to guess by brute force. - * - * Can be generated eg using - * - * ```sh - * node -p 'require("crypto").randomBytes(24).toString("base64")' - * ``` - * - * @visibility secret - */ - secret: string; - - /** - * Sets the subject of the principal, when matching this token. - * Useful for debugging and tracking purposes. - */ - subject: 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; - }; - }>; - } - | { - /** - * This access method consists of random static tokens that can be - * handed out to callers. - * - * The tokens are then passed along verbatim with requests in the - * Authorization header: - * - * ``` - * Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW - * ``` - */ - type: 'static'; - options: { - /** - * A raw token that can be any string, but for security reasons - * should be sufficiently long so as not to be easy to guess by - * brute force. - * - * Can be generated eg using - * - * ```sh - * node -p 'require("crypto").randomBytes(24).toString("base64")' - * ``` - * - * Since the tokens can be any string, you are free to add - * additional identifying data to them if you like. For example, - * adding a `freben-local-dev-` prefix for debugging purposes to a - * token that you know will be handed out for use as a personal - * access token during development. - * - * @visibility secret - */ - token: string; - - /** - * Sets the subject of the principal, when matching this token. - * Useful for debugging and tracking purposes. - */ - subject: 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; - }; - }>; - } - | { - /** - * This access method consists of a JWKS endpoint that can be used to - * verify JWT tokens. - * - * Callers generate JWT tokens via 3rd party tooling - * and pass them in the Authorization header: - * - * ``` - * Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW - * ``` - */ - type: 'jwks'; - options: { - /** - * The full URL of the JWKS endpoint. - */ - url: string; - /** - * Sets the algorithm(s) that should be used to verify the JWT tokens. - * The passed JWTs must have been signed using one of the listed algorithms. - */ - algorithm?: string | string[]; - /** - * Sets the issuer(s) that should be used to verify the JWT tokens. - * Passed JWTs must have an `iss` claim which matches one of the specified issuers. - */ - issuer?: string | string[]; - /** - * Sets the audience(s) that should be used to verify the JWT tokens. - * The passed JWTs must have an "aud" claim that matches one of the audiences specified, - * or have no audience specified. - */ - audience?: string | string[]; - /** - * Sets an optional subject prefix. Passes the subject to called plugins. - * Useful for debugging and tracking purposes. - */ - subjectPrefix?: string; - }; - } - >; - }; - packages?: 'all' | { include?: string[]; exclude?: string[] }; - }; - - /** Discovery options. */ - discovery?: { - /** - * Endpoints - * - * A list of target baseUrls and the associated plugins. - */ - endpoints: { - /** - * The target baseUrl to use for the plugin - * - * Can be either a string or an object with internal and external keys. - * Targets with `{{pluginId}}` or `{{ pluginId }} in the url will be replaced with the pluginId. - */ - target: string | { internal: string; external: string }; - /** Array of plugins which use the target baseUrl. */ - plugins: string[]; - }[]; + /** Used by the feature discovery service */ + packages?: + | 'all' + | { + include?: string[]; + exclude?: string[]; + }; }; } diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index e809d1c2f0..6582a60515 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.8.1-next.2", + "version": "0.10.0-next.1", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" @@ -52,9 +52,7 @@ "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:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", @@ -62,14 +60,11 @@ "@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", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "fs-extra": "^11.2.0", "helmet": "^6.0.0", "jose": "^5.0.0", "knex": "^3.0.0", @@ -94,7 +89,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/compression": "^1.7.0", - "@types/fs-extra": "^11.0.0", "@types/http-errors": "^2.0.0", "@types/minimist": "^1.2.0", "@types/morgan": "^1.9.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/alpha/featureDiscoveryServiceFactory.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts index c16a3f6534..05de659083 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts @@ -15,145 +15,17 @@ */ import { - BackendFeature, - RootConfigService, - RootLoggerService, coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { - featureDiscoveryServiceRef, - FeatureDiscoveryService, -} from '@backstage/backend-plugin-api/alpha'; -import { resolve as resolvePath, dirname } from 'path'; -import fs from 'fs-extra'; -import { BackstagePackageJson } from '@backstage/cli-node'; +import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { PackageDiscoveryService } from '../../../backend-defaults/src/PackageDiscoveryService'; -const DETECTED_PACKAGE_ROLES = [ - 'node-library', - 'backend', - 'backend-plugin', - 'backend-plugin-module', -]; - -/** @internal */ -async function findClosestPackageDir( - searchDir: string, -): Promise { - let path = searchDir; - - // Some confidence check to avoid infinite loop - for (let i = 0; i < 1000; i++) { - const packagePath = resolvePath(path, 'package.json'); - const exists = await fs.pathExists(packagePath); - if (exists) { - return path; - } - - const newPath = dirname(path); - if (newPath === path) { - return undefined; - } - path = newPath; - } - - throw new Error( - `Iteration limit reached when searching for root package.json at ${searchDir}`, - ); -} - -/** @internal */ -class PackageDiscoveryService implements FeatureDiscoveryService { - constructor( - private readonly config: RootConfigService, - private readonly logger: RootLoggerService, - ) {} - - getDependencyNames(path: string) { - const { dependencies } = require(path) as BackstagePackageJson; - const packagesConfig = this.config.getOptional('backend.packages'); - - const dependencyNames = Object.keys(dependencies || {}); - - if (packagesConfig === 'all') { - return dependencyNames; - } - - const includedPackagesConfig = this.config.getOptionalStringArray( - 'backend.packages.include', - ); - - const includedPackages = includedPackagesConfig - ? new Set(includedPackagesConfig) - : dependencyNames; - const excludedPackagesSet = new Set( - this.config.getOptionalStringArray('backend.packages.exclude'), - ); - - return [...includedPackages].filter(name => !excludedPackagesSet.has(name)); - } - - async getBackendFeatures(): Promise<{ features: Array }> { - const packagesConfig = this.config.getOptional('backend.packages'); - if (!packagesConfig || Object.keys(packagesConfig).length === 0) { - return { features: [] }; - } - - const packageDir = await findClosestPackageDir(process.argv[1]); - if (!packageDir) { - throw new Error('Package discovery failed to find package.json'); - } - const dependencyNames = this.getDependencyNames( - resolvePath(packageDir, 'package.json'), - ); - - const features: BackendFeature[] = []; - - for (const name of dependencyNames) { - const depPkg = require(require.resolve(`${name}/package.json`, { - paths: [packageDir], - })) as BackstagePackageJson; - if ( - !depPkg?.backstage?.role || - !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role) - ) { - continue; // Not a backstage backend package, ignore - } - - const exportedModulePaths = [ - require.resolve(name, { - paths: [packageDir], - }), - ]; - - // Find modules exported as alpha - try { - exportedModulePaths.push( - require.resolve(`${name}/alpha`, { paths: [packageDir] }), - ); - } catch { - /* ignore */ - } - - for (const modulePath of exportedModulePaths) { - const mod = require(modulePath); - - if (isBackendFeature(mod.default)) { - this.logger.info(`Detected: ${name}`); - features.push(mod.default); - } - if (isBackendFeatureFactory(mod.default)) { - this.logger.info(`Detected: ${name}`); - features.push(mod.default()); - } - } - } - - return { features }; - } -} - -/** @alpha */ +/** + * @alpha + * @deprecated The `featureDiscoveryServiceFactory` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead. + */ export const featureDiscoveryServiceFactory = createServiceFactory({ service: featureDiscoveryServiceRef, deps: { @@ -164,21 +36,3 @@ export const featureDiscoveryServiceFactory = createServiceFactory({ return new PackageDiscoveryService(config, logger); }, }); - -function isBackendFeature(value: unknown): value is BackendFeature { - return ( - !!value && - ['object', 'function'].includes(typeof value) && - (value as BackendFeature).$$type === '@backstage/BackendFeature' - ); -} - -function isBackendFeatureFactory( - value: unknown, -): value is () => BackendFeature { - return ( - !!value && - typeof value === 'function' && - (value as any).$$type === '@backstage/BackendFeatureFactory' - ); -} diff --git a/packages/backend-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/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/cache/index.ts b/packages/backend-app-api/src/services/implementations/cache/index.ts deleted file mode 100644 index f96ee77182..0000000000 --- a/packages/backend-app-api/src/services/implementations/cache/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 { cacheServiceFactory } from './cacheServiceFactory'; 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 0088e53f98..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.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 { httpRouterServiceFactory as _httpRouterServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory'; - -/** - * 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 bc12480ae8..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts +++ /dev/null @@ -1,19 +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 { 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/index.ts b/packages/backend-app-api/src/services/implementations/index.ts deleted file mode 100644 index 3a2fe819ed..0000000000 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ /dev/null @@ -1,28 +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. - */ - -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'; - -export * from './deprecated'; 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 b16509cf96..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.getSubject(); - 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 dc52130ec6..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, - ).getSubject(); - - 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 cf8845fcec..b524301f21 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -39,14 +39,14 @@ class MockLogger { } const baseFactories = [ - lifecycleServiceFactory(), - rootLifecycleServiceFactory(), + lifecycleServiceFactory, + rootLifecycleServiceFactory, createServiceFactory({ service: coreServices.rootLogger, deps: {}, factory: () => new MockLogger(), - })(), - loggerServiceFactory(), + }), + loggerServiceFactory, ]; const testPlugin = createBackendPlugin({ @@ -57,7 +57,7 @@ const testPlugin = createBackendPlugin({ async init() {}, }); }, -})(); +}); describe('BackendInitializer', () => { it('should initialize root scoped services', async () => { @@ -84,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); @@ -249,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); @@ -505,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( @@ -576,4 +576,109 @@ 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 reject plugins with missing dependencies', async () => { + const init = new BackendInitializer(baseFactories); + const ref = createServiceRef({ id: 'a' }); + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: { ref }, + async init() {}, + }); + }, + }), + ); + await expect(init.start()).rejects.toThrow( + "Service or extension point dependencies of plugin 'test' are missing for the following ref(s): serviceRef{a}", + ); + }); + + it('should reject modules with missing dependencies', async () => { + const init = new BackendInitializer(baseFactories); + const ref = createServiceRef({ id: 'a' }); + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'test', + moduleId: 'test-mod', + register(reg) { + reg.registerInit({ + deps: { ref }, + async init() {}, + }); + }, + }), + ); + await expect(init.start()).rejects.toThrow( + "Service or extension point dependencies of module 'test-mod' for plugin 'test' are missing for the following ref(s): serviceRef{a}", + ); + }); + + 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 c0da9eda62..7e29276a77 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -38,6 +38,7 @@ 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; @@ -92,8 +93,11 @@ export class BackendInitializer { if (missingRefs.size > 0) { const missing = Array.from(missingRefs).join(', '); + const target = moduleId + ? `module '${moduleId}' for plugin '${pluginId}'` + : `plugin '${pluginId}'`; throw new Error( - `No extension point or service available for the following ref(s): ${missing}`, + `Service or extension point dependencies of ${target} are missing for the following ref(s): ${missing}`, ); } @@ -156,6 +160,7 @@ export class BackendInitializer { } const featureDiscovery = await this.#serviceRegistry.get( + // TODO: Let's leave this in place and remove it once the deprecated service is removed. We can do that post-1.0 since it's alpha featureDiscoveryServiceRef, 'root', ); @@ -163,7 +168,7 @@ 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(); } @@ -417,6 +422,7 @@ export class BackendInitializer { const result = await loader .loader(Object.fromEntries(deps)) + .then(features => features.map(unwrapFeature)) .catch(error => { throw new ForwardedError( `Feature loader ${loader.description} failed`, 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/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 075eb58b3f..bd761ff3c8 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -26,18 +26,6 @@ import { */ export interface Backend { add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void; - /** - * @deprecated The ability to add features defined as a callback is being - * removed. Please update the installed feature to no longer be defined as a - * callback, typically this means updating it to use the latest version of - * `@backstage/backend-plugin-api`. If the feature is from a third-party - * package, please reach out to the package maintainer to update it. - */ - add( - feature: - | (() => BackendFeature) - | Promise<{ default: () => BackendFeature }>, - ): void; start(): Promise; stop(): Promise; } diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 5eadae4c0f..2a488efd04 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,142 @@ # @backstage/backend-common +## 0.25.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 988c145: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + - The deprecated `dropDatabase` function has now been removed, without replacement. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.25.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 8ba77ed: The `legacyPlugin` and `makeLegacyPlugin` helpers now provide their own shim implementation of the identity and token manager services, as these services are being removed from the new backend system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 2e9ec14: Add `pg-format` as a dependency +- 19ff127: Internal refactor to re-declare the token manager service which was removed from `@backstage/backend-plugin-api`, but is still supported in this package for backwards compatibility. +- 66dbf0a: Allow the cache service to accept the human duration format for TTL +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 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 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 23ed13b62c..1a67d4877f 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; @@ -119,10 +65,15 @@ export type CacheClientOptions = CacheServiceOptions; // @public @deprecated (undocumented) export type CacheClientSetOptions = CacheServiceSetOptions; -// Warning: (ae-forgotten-export) The symbol "CacheManager_2" needs to be exported by the entry point index.d.ts -// // @public @deprecated (undocumented) -export class CacheManager extends CacheManager_2 {} +export class CacheManager { + // (undocumented) + forPlugin(pluginId: string): PluginCacheManager; + static fromConfig( + config: RootConfigService, + options?: CacheManagerOptions, + ): CacheManager; +} // Warning: (ae-forgotten-export) The symbol "CacheManagerOptions_2" needs to be exported by the entry point index.d.ts // @@ -142,13 +93,18 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise; } +// 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; }, @@ -206,7 +162,10 @@ export class DatabaseManager implements LegacyRootDatabaseService { // (undocumented) static fromConfig( config: Config, - options?: DatabaseManagerOptions, + options?: { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; + }, ): DatabaseManager; } @@ -222,11 +181,6 @@ export class DockerContainerRunner implements ContainerRunner { runContainer(options: RunContainerOptions): Promise; } -// Warning: (ae-forgotten-export) The symbol "dropDatabase_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const dropDatabase: typeof dropDatabase_2; - // @public @deprecated export function errorHandler( options?: ErrorHandlerOptions, @@ -239,21 +193,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,34 +275,9 @@ 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( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; + static fromConfig(config: Config): HostDiscovery; // (undocumented) getBaseUrl(pluginId: string): Promise; // (undocumented) @@ -404,6 +318,23 @@ export type KubernetesContainerRunnerOptions = { // @public @deprecated (undocumented) export type LegacyCreateRouter = (deps: TEnv) => Promise; +// @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, @@ -418,9 +349,7 @@ export const legacyPlugin: ( logger: LoggerService; permissions: PermissionsService; scheduler: SchedulerService; - tokenManager: TokenManagerService; reader: UrlReaderService; - identity: IdentityService; }, { logger: (log: LoggerService) => Logger; @@ -428,15 +357,18 @@ 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 -// // @public @deprecated (undocumented) -export type LegacyRootDatabaseService = LegacyRootDatabaseService_2; +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; // @public @deprecated export function loadBackendConfig(options: { @@ -467,17 +399,22 @@ export function makeLegacyPlugin< ): ( name: string, createRouterImport: Promise<{ - default: LegacyCreateRouter>; + default: LegacyCreateRouter< + TransformedEnv & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + } + >; }>, -) => BackendFeatureCompat; +) => BackendFeature; // @public @deprecated export function notFoundHandler(): RequestHandler; -// Warning: (ae-forgotten-export) The symbol "PluginCacheManager_2" needs to be exported by the entry point index.d.ts -// // @public @deprecated (undocumented) -export type PluginCacheManager = PluginCacheManager_2; +export type PluginCacheManager = { + getClient(options?: CacheServiceOptions): CacheService; +}; // @public @deprecated (undocumented) export type PluginDatabaseManager = DatabaseService; @@ -500,50 +437,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 +470,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 +543,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/config.d.ts b/packages/backend-common/config.d.ts index ed253dd54d..6146adfe83 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { app: { baseUrl: string; // defined in core, but repeated here without doc @@ -180,7 +182,7 @@ export interface Config { | { store: 'memory'; /** An optional default TTL (in milliseconds). */ - defaultTtl?: number; + defaultTtl?: number | HumanDuration; } | { store: 'redis'; @@ -190,7 +192,7 @@ export interface Config { */ connection: string; /** An optional default TTL (in milliseconds). */ - defaultTtl?: number; + defaultTtl?: number | HumanDuration; /** * Whether or not [useRedisSets](https://github.com/jaredwray/keyv/tree/main/packages/redis#useredissets) should be configured to this redis cache. * Defaults to true if unspecified. @@ -205,9 +207,12 @@ export interface Config { */ connection: string; /** An optional default TTL (in milliseconds). */ - defaultTtl?: number; + defaultTtl?: number | HumanDuration; }; + /** + * Properties returned upon CORS requests to the backend, including the app-backend. + */ cors?: { origin?: string | string[]; methods?: string | string[]; @@ -219,6 +224,16 @@ export interface Config { optionsSuccessStatus?: number; }; + /** + * Content Security Policy options. + * + * The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The + * values are on the format that the helmet library expects them, as an + * array of strings. There is also the special value false, which means to + * remove the default value that Backstage puts in place for that policy. + */ + csp?: { [policyId: string]: string[] | false }; + /** * Configuration related to URL reading, used for example for reading catalog info * files, scaffolder templates, and techdocs content. @@ -246,15 +261,5 @@ export interface Config { paths?: string[]; }>; }; - - /** - * Content Security Policy options. - * - * The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The - * values are on the format that the helmet library expects them, as an - * array of strings. There is also the special value false, which means to - * remove the default value that Backstage puts in place for that policy. - */ - csp?: { [policyId: string]: string[] | false }; }; } diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index ccf44fba0f..1e052c8eba 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.23.4-next.2", + "version": "0.25.0-next.1", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" @@ -107,6 +107,7 @@ "p-limit": "^3.1.0", "path-to-regexp": "^6.2.1", "pg": "^8.11.3", + "pg-format": "^1.0.4", "raw-body": "^2.4.1", "selfsigned": "^2.0.0", "stoppable": "^1.1.0", 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 15cd881649..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( @@ -299,7 +298,7 @@ export function createLegacyAuthAdapters< auth?: AuthService; httpAuth?: HttpAuthService; userInfo?: UserInfoService; - identity?: IdentityService; + identity?: LegacyIdentityService; tokenManager?: TokenManager; discovery: PluginEndpointDiscovery; }, 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 4dc7d1a09a..bf051f3040 100644 --- a/packages/backend-common/src/compat/legacy/legacy.ts +++ b/packages/backend-common/src/compat/legacy/legacy.ts @@ -18,13 +18,16 @@ import { AuthService, coreServices, createBackendPlugin, - HttpRouterService, + 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 @@ -46,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(); @@ -64,7 +72,64 @@ 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, + }, + }; + }, + }; } /** @@ -87,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]) => { @@ -107,25 +187,35 @@ export function makeLegacyPlugin< if (transform) { return [key, transform(dep)]; } - if (key === 'tokenManager') { - return [ - key, - wrapTokenManager(dep as TokenManager, _auth as AuthService), - ]; - } 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 as HttpRouterService).use(router); + pluginEnv.identity = createIdentityServiceShim(auth, userInfo); + + const pluginRouter = await createRouter( + pluginEnv as TransformedEnv & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + }, + ); + router.use(pluginRouter); }, }); }, }); - - return compatPlugin(); }; } @@ -155,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/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..f769d4db09 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -23,58 +23,14 @@ import { HostDiscovery as _HostDiscovery } from '../../../backend-defaults/src/e import { CacheManager as _CacheManager } from '../../../backend-defaults/src/entrypoints/cache/CacheManager'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - type PluginCacheManager as _PluginCacheManager, - type CacheManagerOptions as _CacheManagerOptions, -} from '../../../backend-defaults/src/entrypoints/cache/types'; +import { type CacheManagerOptions as _CacheManagerOptions } from '../../../backend-defaults/src/entrypoints/cache/types'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - dropDatabase as _dropDatabase, DatabaseManager as _DatabaseManager, type DatabaseManagerOptions as _DatabaseManagerOptions, - 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,18 +41,11 @@ 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, + DatabaseService, + LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; export * from './hot'; @@ -147,11 +96,11 @@ export class HostDiscovery implements DiscoveryService { * plugins: [search] * ``` * - * The basePath defaults to `/api`, meaning the default full internal + * The fixed base path is `/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)); + static fromConfig(config: Config) { + return new HostDiscovery(_HostDiscovery.fromConfig(config)); } private constructor(private readonly impl: _HostDiscovery) {} @@ -182,7 +131,31 @@ export { HostDiscovery as SingleHostDiscovery }; * @public * @deprecated Use `CacheManager` from the `@backstage/backend-defaults` package instead */ -export class CacheManager extends _CacheManager {} +export class CacheManager { + /** + * Creates a new {@link CacheManager} instance by reading from the `backend` + * config section, specifically the `.cache` key. + * + * @param config - The loaded application configuration. + */ + static fromConfig( + config: RootConfigService, + options: CacheManagerOptions = {}, + ): CacheManager { + return new CacheManager(_CacheManager.fromConfig(config, options)); + } + + private constructor(private readonly _impl: _CacheManager) {} + + forPlugin(pluginId: string): PluginCacheManager { + return { + getClient: options => { + const result = this._impl.forPlugin(pluginId); + return options ? result.withOptions(options) : result; + }, + }; + } +} /** * @public @@ -194,7 +167,9 @@ export type CacheManagerOptions = _CacheManagerOptions; * @public * @deprecated Use `PluginCacheManager` from the `@backstage/backend-defaults` package instead */ -export type PluginCacheManager = _PluginCacheManager; +export type PluginCacheManager = { + getClient(options?: CacheServiceOptions): CacheService; +}; /** * @public @@ -219,14 +194,20 @@ export type CacheClientOptions = CacheServiceOptions; * @deprecated Use `DatabaseManager` from the `@backstage/backend-defaults` package instead */ export class DatabaseManager implements LegacyRootDatabaseService { - private constructor(private readonly _databaseManager: _DatabaseManager) {} + private constructor( + private readonly _databaseManager: _DatabaseManager, + private readonly logger?: LoggerService, + ) {} static fromConfig( config: Config, - options?: DatabaseManagerOptions, + options?: { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; + }, ): DatabaseManager { const _databaseManager = _DatabaseManager.fromConfig(config, options); - return new DatabaseManager(_databaseManager); + return new DatabaseManager(_databaseManager, options?.logger); } forPlugin( @@ -235,7 +216,20 @@ export class DatabaseManager implements LegacyRootDatabaseService { | { lifecycle: LifecycleService; pluginMetadata: PluginMetadataService } | undefined, ): PluginDatabaseManager { - return this._databaseManager.forPlugin(pluginId, deps); + const logger: LoggerService = this.logger ?? { + debug() {}, + info() {}, + warn() {}, + error() {}, + child() { + return this; + }, + }; + const lifecycle: LifecycleService = deps?.lifecycle ?? { + addShutdownHook() {}, + addStartupHook() {}, + }; + return this._databaseManager.forPlugin(pluginId, { logger, lifecycle }); } } @@ -253,15 +247,11 @@ export type PluginDatabaseManager = _PluginDatabaseManager; /** * @public - * @deprecated Use `LegacyRootDatabaseService` from the `@backstage/backend-defaults` package instead + * @deprecated Use `DatabaseManager` from `@backstage/backend-defaults/database` instead, or migrate to the new backend system and use `coreServices.database` */ -export type LegacyRootDatabaseService = _LegacyRootDatabaseService; - -/** - * @public - * @deprecated Use `dropDatabase` from the `@backstage/backend-defaults` package instead - */ -export const dropDatabase = _dropDatabase; +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; /** * @public @@ -290,184 +280,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 44b0a6b9b0..d9fa0e99f6 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,211 @@ # @backstage/backend-defaults +## 0.5.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 055b75b: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + For the `/database` subpath exports: + + - The deprecated `dropDatabase` function has now been removed, without replacement. + - The deprecated `LegacyRootDatabaseService` type has now been removed. + - The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. + - `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + + For the `/cache` subpath exports: + + - The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. + - Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. + +### Patch Changes + +- 622360e: Move down the discovery config to be in the root +- fe6fd8c: Accept `ConfigService` instead of `Config` in constructors/factories +- 5705424: Wrap scheduled tasks from the scheduler core service now in OpenTelemetry spans +- b2a329d: Properly indent the config schema +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.5.0-next.0 + +### Minor Changes + +- 359fcd7: **BREAKING**: The backwards compatibility with plugins using legacy auth through the token manager service has been removed. This means that instead of falling back to using the old token manager, requests towards plugins that don't support the new auth system will simply fail. Please make sure that all plugins in your deployment are hosted within a backend instance from the new backend system. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +- 19ff127: **BREAKING**: The default backend instance no longer provides implementations for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. + + If you rely on plugins that still require these services, you can add them to your own backend by re-creating the service reference and factory. + + The following can be used to implement the identity service: + + ```ts + import { + coreServices, + createServiceFactory, + createServiceRef, + } from '@backstage/backend-plugin-api'; + import { + DefaultIdentityClient, + IdentityApi, + } from '@backstage/plugin-auth-node'; + + backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.identity' }), + deps: { + discovery: coreServices.discovery, + }, + async factory({ discovery }) { + return DefaultIdentityClient.create({ discovery }); + }, + }), + ); + ``` + + The following can be used to implement the token manager service: + + ```ts + import { ServerTokenManager, TokenManager } from '@backstage/backend-common'; + import { createBackend } from '@backstage/backend-defaults'; + import { + coreServices, + createServiceFactory, + createServiceRef, + } from '@backstage/backend-plugin-api'; + + backend.add( + createServiceFactory({ + service: createServiceRef({ id: 'core.tokenManager' }), + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + createRootContext({ config, logger }) { + return ServerTokenManager.fromConfig(config, { + logger, + allowDisabledTokenManager: true, + }); + }, + async factory(_deps, tokenManager) { + return tokenManager; + }, + }), + ); + ``` + +### Patch Changes + +- 7f779c7: `auth.externalAccess` should be optional in the config schema +- 7a72ec8: Exports the `discoveryFeatureLoader` as a replacement for the deprecated `featureDiscoveryService`. + The `discoveryFeatureLoader` is a new backend system [feature loader](https://backstage.io/docs/backend-system/architecture/feature-loaders/) that discovers backend features from the current `package.json` and its dependencies. + Here is an example using the `discoveryFeatureLoader` loader in a new backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { discoveryFeatureLoader } from '@backstage/backend-defaults'; + //... + + const backend = createBackend(); + //... + backend.add(discoveryFeatureLoader); + //... + backend.start(); + ``` + +- 66dbf0a: Allow the cache service to accept the human duration format for TTL +- 5a8fcb4: Added the option to skip database migrations by setting `skipMigrations: true` in config. This can be done globally in the database config or by plugin id. +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 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 diff --git a/packages/backend-defaults/api-report-auth.md b/packages/backend-defaults/api-report-auth.md index 5ceca7f72e..2f201e4573 100644 --- a/packages/backend-defaults/api-report-auth.md +++ b/packages/backend-defaults/api-report-auth.md @@ -4,14 +4,13 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const authServiceFactory: ServiceFactoryCompat< +export const authServiceFactory: ServiceFactory< AuthService, 'plugin', - 'singleton', - undefined + '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 e8b6f075ab..ecda42f983 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -4,16 +4,15 @@ ```ts import { CacheService } from '@backstage/backend-plugin-api'; -import { CacheServiceOptions } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; + forPlugin(pluginId: string): CacheService; static fromConfig( - config: Config, + config: RootConfigService, options?: CacheManagerOptions, ): CacheManager; } @@ -25,18 +24,11 @@ export type CacheManagerOptions = { }; // @public -export const cacheServiceFactory: ServiceFactoryCompat< +export const cacheServiceFactory: ServiceFactory< CacheService, 'plugin', - 'singleton', - undefined + 'singleton' >; -// @public (undocumented) -export interface PluginCacheManager { - // (undocumented) - getClient(options?: CacheServiceOptions): CacheService; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 8d204db025..d9e9e9e287 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -3,24 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Config } from '@backstage/config'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginMetadataService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export class DatabaseManager implements LegacyRootDatabaseService { +export class DatabaseManager { forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): DatabaseService; static fromConfig( - config: Config, + config: RootConfigService, options?: DatabaseManagerOptions, ): DatabaseManager; } @@ -28,27 +27,14 @@ export class DatabaseManager implements LegacyRootDatabaseService { // @public export type DatabaseManagerOptions = { migrations?: DatabaseService['migrations']; - logger?: LoggerService; }; // @public -export const databaseServiceFactory: ServiceFactoryCompat< +export const databaseServiceFactory: ServiceFactory< DatabaseService, 'plugin', - 'singleton', - undefined + 'singleton' >; -// @public @deprecated -export function dropDatabase( - dbConfig: Config, - ...databaseNames: string[] -): Promise; - -// @public -export type LegacyRootDatabaseService = { - forPlugin(pluginId: string): DatabaseService; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-discovery.md b/packages/backend-defaults/api-report-discovery.md index 1b19a42aa5..d168d33a71 100644 --- a/packages/backend-defaults/api-report-discovery.md +++ b/packages/backend-defaults/api-report-discovery.md @@ -3,26 +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 { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const discoveryServiceFactory: ServiceFactoryCompat< +export const discoveryServiceFactory: ServiceFactory< DiscoveryService, 'plugin', - 'singleton', - undefined + 'singleton' >; // @public export class HostDiscovery implements DiscoveryService { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; + static fromConfig(config: RootConfigService): HostDiscovery; // (undocumented) getBaseUrl(pluginId: string): Promise; // (undocumented) diff --git a/packages/backend-defaults/api-report-httpAuth.md b/packages/backend-defaults/api-report-httpAuth.md index a65a6e157d..6c08a6f4d0 100644 --- a/packages/backend-defaults/api-report-httpAuth.md +++ b/packages/backend-defaults/api-report-httpAuth.md @@ -4,14 +4,13 @@ ```ts import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const httpAuthServiceFactory: ServiceFactoryCompat< +export const httpAuthServiceFactory: ServiceFactory< HttpAuthService, 'plugin', - 'singleton', - undefined + '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 300e75a44d..dc612377ae 100644 --- a/packages/backend-defaults/api-report-httpRouter.md +++ b/packages/backend-defaults/api-report-httpRouter.md @@ -7,7 +7,7 @@ import { HttpRouterService } from '@backstage/backend-plugin-api'; import { HumanDuration } from '@backstage/types'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public export function createLifecycleMiddleware( @@ -15,11 +15,10 @@ export function createLifecycleMiddleware( ): RequestHandler; // @public -export const httpRouterServiceFactory: ServiceFactoryCompat< +export const httpRouterServiceFactory: ServiceFactory< HttpRouterService, 'plugin', - 'singleton', - undefined + 'singleton' >; // @public diff --git a/packages/backend-defaults/api-report-lifecycle.md b/packages/backend-defaults/api-report-lifecycle.md index 54ffb77fed..0d909faa90 100644 --- a/packages/backend-defaults/api-report-lifecycle.md +++ b/packages/backend-defaults/api-report-lifecycle.md @@ -4,14 +4,13 @@ ```ts import { LifecycleService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const lifecycleServiceFactory: ServiceFactoryCompat< +export const lifecycleServiceFactory: ServiceFactory< LifecycleService, 'plugin', - 'singleton', - undefined + '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 40ca4e21b4..c3d2ab3359 100644 --- a/packages/backend-defaults/api-report-logger.md +++ b/packages/backend-defaults/api-report-logger.md @@ -4,14 +4,13 @@ ```ts import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const loggerServiceFactory: ServiceFactoryCompat< +export const loggerServiceFactory: ServiceFactory< LoggerService, 'plugin', - 'singleton', - undefined + '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 eea11ed7c9..003bee9d25 100644 --- a/packages/backend-defaults/api-report-permissions.md +++ b/packages/backend-defaults/api-report-permissions.md @@ -4,14 +4,13 @@ ```ts import { PermissionsService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const permissionsServiceFactory: ServiceFactoryCompat< +export const permissionsServiceFactory: ServiceFactory< PermissionsService, 'plugin', - 'singleton', - undefined + 'singleton' >; // (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 c7580cacd6..b26de7cfda 100644 --- a/packages/backend-defaults/api-report-rootHealth.md +++ b/packages/backend-defaults/api-report-rootHealth.md @@ -4,14 +4,13 @@ ```ts import { RootHealthService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) -export const rootHealthServiceFactory: ServiceFactoryCompat< +export const rootHealthServiceFactory: ServiceFactory< RootHealthService, 'root', - 'singleton', - undefined + 'singleton' >; // (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 e522471d04..7a7c4fd7c0 100644 --- a/packages/backend-defaults/api-report-rootLifecycle.md +++ b/packages/backend-defaults/api-report-rootLifecycle.md @@ -4,14 +4,13 @@ ```ts import { RootLifecycleService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const rootLifecycleServiceFactory: ServiceFactoryCompat< +export const rootLifecycleServiceFactory: ServiceFactory< RootLifecycleService, 'root', - 'singleton', - undefined + '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 3878420192..b353039c03 100644 --- a/packages/backend-defaults/api-report-rootLogger.md +++ b/packages/backend-defaults/api-report-rootLogger.md @@ -7,15 +7,14 @@ import { Format } from 'logform'; import { JsonObject } from '@backstage/types'; import { LoggerService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; // @public -export const rootLoggerServiceFactory: ServiceFactoryCompat< +export const rootLoggerServiceFactory: ServiceFactory< RootLoggerService, 'root', - 'singleton', - undefined + 'singleton' >; // @public diff --git a/packages/backend-defaults/api-report-scheduler.md b/packages/backend-defaults/api-report-scheduler.md index 9953518e09..10c0c0a943 100644 --- a/packages/backend-defaults/api-report-scheduler.md +++ b/packages/backend-defaults/api-report-scheduler.md @@ -6,7 +6,7 @@ import { DatabaseService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public export class DefaultSchedulerService { @@ -18,11 +18,10 @@ export class DefaultSchedulerService { } // @public -export const schedulerServiceFactory: ServiceFactoryCompat< +export const schedulerServiceFactory: ServiceFactory< SchedulerService, 'plugin', - 'singleton', - undefined + '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 f561c2cbf5..f8b8db2954 100644 --- a/packages/backend-defaults/api-report-urlReader.md +++ b/packages/backend-defaults/api-report-urlReader.md @@ -12,7 +12,6 @@ import { AzureIntegration } from '@backstage/integration'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; -import { Config } from '@backstage/config'; import { GerritIntegration } from '@backstage/integration'; import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -21,7 +20,8 @@ import { GitLabIntegration } from '@backstage/integration'; import { HarnessIntegration } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Readable } from 'stream'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; +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'; @@ -360,7 +360,7 @@ export class HarnessUrlReader implements UrlReaderService { // @public export type ReaderFactory = (options: { - config: Config; + config: RootConfigService; logger: LoggerService; treeResponseFactory: ReadTreeResponseFactory; }) => UrlReaderPredicateTuple[]; @@ -434,16 +434,15 @@ export class UrlReaders { } // @public -export const urlReaderServiceFactory: ServiceFactoryCompat< +export const urlReaderServiceFactory: ServiceFactory< UrlReaderService, 'plugin', - 'singleton', - undefined + 'singleton' >; // @public export type UrlReadersOptions = { - config: Config; + config: RootConfigService; logger: LoggerService; factories?: ReaderFactory[]; }; diff --git a/packages/backend-defaults/api-report-userInfo.md b/packages/backend-defaults/api-report-userInfo.md index 27f73d06e1..9ab4db1391 100644 --- a/packages/backend-defaults/api-report-userInfo.md +++ b/packages/backend-defaults/api-report-userInfo.md @@ -3,15 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; // @public -export const userInfoServiceFactory: ServiceFactoryCompat< +export const userInfoServiceFactory: ServiceFactory< UserInfoService, 'plugin', - 'singleton', - undefined + 'singleton' >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md index ab9cc5775c..def38beda7 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -4,7 +4,11 @@ ```ts import { Backend } from '@backstage/backend-app-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public (undocumented) export function createBackend(): Backend; + +// @public +export const discoveryFeatureLoader: BackendFeature; ``` diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 1c784bce0c..a8c6e1c290 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -14,8 +14,51 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { + app: { + baseUrl: string; // defined in core, but repeated here without doc + }; + backend?: { + /** + * The full base URL of the backend, as seen from the browser's point of + * view as it makes calls to the backend. + */ + baseUrl: string; + + /** Address that the backend should listen to. */ + listen?: + | string + | { + /** Address of the interface that the backend should bind to. */ + host?: string; + /** Port that the backend should listen to. */ + port?: string | number; + }; + + /** + * HTTPS configuration for the backend. If omitted the backend will serve HTTP. + * + * Setting this to `true` will cause self-signed certificates to be generated, which + * can be useful for local development or other non-production scenarios. + */ + https?: + | true + | { + /** Certificate configuration */ + certificate?: { + /** PEM encoded certificate. Use $file to load in a file */ + cert: string; + /** + * PEM encoded certificate key. Use $file to load in a file. + * @visibility secret + */ + key: string; + }; + }; + /** * Options used by the default auth, httpAuth and userInfo services. */ @@ -74,7 +117,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 @@ -330,6 +373,184 @@ export interface Config { } >; }; + + /** Database connection configuration, select base database type using the `client` field */ + database: { + /** Default database client to use */ + client: 'better-sqlite3' | 'sqlite3' | 'pg'; + /** + * Base database connection string, or object with individual connection properties + * @visibility secret + */ + connection: + | string + | { + /** + * Password that belongs to the client User + * @visibility secret + */ + password?: string; + /** + * Other connection settings + */ + [key: string]: unknown; + }; + /** Database name prefix override */ + prefix?: string; + /** + * Whether to ensure the given database exists by creating it if it does not. + * Defaults to true if unspecified. + */ + ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; + /** + * How plugins databases are managed/divided in the provided database instance. + * + * `database` -> Plugins are each given their own database to manage their schemas/tables. + * + * `schema` -> Plugins will be given their own schema (in the specified/default database) + * to manage their tables. + * + * NOTE: Currently only supported by the `pg` client. + * + * @default database + */ + pluginDivisionMode?: 'database' | 'schema'; + /** Configures the ownership of newly created schemas in pg databases. */ + role?: string; + /** + * Arbitrary config object to pass to knex when initializing + * (https://knexjs.org/#Installation-client). Most notable is the debug + * and asyncStackTraces booleans + */ + knexConfig?: object; + /** Skip running database migrations. */ + skipMigrations?: boolean; + /** Plugin specific database configuration and client override */ + plugin?: { + [pluginId: string]: { + /** Database client override */ + client?: 'better-sqlite3' | 'sqlite3' | 'pg'; + /** + * Database connection string or Knex object override + * @visibility secret + */ + connection?: string | object; + /** + * Whether to ensure the given database exists by creating it if it does not. + * Defaults to base config if unspecified. + */ + ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; + /** + * Arbitrary config object to pass to knex when initializing + * (https://knexjs.org/#Installation-client). Most notable is the + * debug and asyncStackTraces booleans. + * + * This is merged recursively into the base knexConfig + */ + knexConfig?: object; + /** Configures the ownership of newly created schemas in pg databases. */ + role?: string; + /** Skip running database migrations. */ + skipMigrations?: boolean; + }; + }; + }; + + /** Cache connection configuration, select cache type using the `store` field */ + cache?: + | { + store: 'memory'; + /** An optional default TTL (in milliseconds). */ + defaultTtl?: number | HumanDuration; + } + | { + store: 'redis'; + /** + * A redis connection string in the form `redis://user:pass@host:port`. + * @visibility secret + */ + connection: string; + /** An optional default TTL (in milliseconds). */ + defaultTtl?: number | HumanDuration; + /** + * Whether or not [useRedisSets](https://github.com/jaredwray/keyv/tree/main/packages/redis#useredissets) should be configured to this redis cache. + * Defaults to true if unspecified. + */ + useRedisSets?: boolean; + } + | { + store: 'memcache'; + /** + * A memcache connection string in the form `user:pass@host:port`. + * @visibility secret + */ + connection: string; + /** An optional default TTL (in milliseconds). */ + defaultTtl?: number | HumanDuration; + }; + + cors?: { + origin?: string | string[]; + methods?: string | string[]; + allowedHeaders?: string | string[]; + exposedHeaders?: string | string[]; + credentials?: boolean; + maxAge?: number; + preflightContinue?: boolean; + optionsSuccessStatus?: number; + }; + + /** + * Content Security Policy options. + * + * The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The + * values are on the format that the helmet library expects them, as an + * array of strings. There is also the special value false, which means to + * remove the default value that Backstage puts in place for that policy. + */ + csp?: { [policyId: string]: string[] | false }; + + /** + * Configuration related to URL reading, used for example for reading catalog info + * files, scaffolder templates, and techdocs content. + */ + reading?: { + /** + * A list of targets to allow outgoing requests to. Users will be able to make + * requests on behalf of the backend to the targets that are allowed by this list. + */ + allow?: Array<{ + /** + * A host to allow outgoing requests to, being either a full host or + * a subdomain wildcard pattern with a leading `*`. For example `example.com` + * and `*.example.com` are valid values, `prod.*.example.com` is not. + * The host may also contain a port, for example `example.com:8080`. + */ + host: string; + + /** + * An optional list of paths. In case they are present only targets matching + * any of them will are allowed. You can use trailing slashes to make sure only + * subdirectories are allowed, for example `/mydir/` will allow targets with + * paths like `/mydir/a` but will block paths like `/mydir2`. + */ + paths?: string[]; + }>; + }; }; /** diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index cef9641de6..2f157580bb 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.4.2-next.2", + "version": "0.5.0-next.1", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -124,6 +124,7 @@ "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli-common": "workspace:^", + "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", @@ -171,6 +172,7 @@ "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 +193,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 47fa09da49..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'; @@ -47,7 +42,6 @@ export const defaultServiceFactories = [ discoveryServiceFactory, httpAuthServiceFactory, httpRouterServiceFactory, - identityServiceFactory, lifecycleServiceFactory, loggerServiceFactory, permissionsServiceFactory, @@ -56,7 +50,6 @@ export const defaultServiceFactories = [ rootLifecycleServiceFactory, rootLoggerServiceFactory, schedulerServiceFactory, - tokenManagerServiceFactory, userInfoServiceFactory, urlReaderServiceFactory, eventsServiceFactory, diff --git a/packages/backend-defaults/src/PackageDiscoveryService.ts b/packages/backend-defaults/src/PackageDiscoveryService.ts new file mode 100644 index 0000000000..d7d127f4fa --- /dev/null +++ b/packages/backend-defaults/src/PackageDiscoveryService.ts @@ -0,0 +1,170 @@ +/* + * 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 fs from 'fs-extra'; +import { resolve as resolvePath, dirname } from 'path'; + +import { + BackendFeature, + RootConfigService, + RootLoggerService, +} from '@backstage/backend-plugin-api'; +import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; +import { BackstagePackageJson } from '@backstage/cli-node'; + +const DETECTED_PACKAGE_ROLES = [ + 'node-library', + 'backend', + 'backend-plugin', + 'backend-plugin-module', +]; + +/** @internal */ +function isBackendFeature(value: unknown): value is BackendFeature { + return ( + !!value && + ['object', 'function'].includes(typeof value) && + (value as BackendFeature).$$type === '@backstage/BackendFeature' + ); +} + +/** @internal */ +function isBackendFeatureFactory( + value: unknown, +): value is () => BackendFeature { + return ( + !!value && + typeof value === 'function' && + (value as any).$$type === '@backstage/BackendFeatureFactory' + ); +} + +/** @internal */ +async function findClosestPackageDir( + searchDir: string, +): Promise { + let path = searchDir; + + // Some confidence check to avoid infinite loop + for (let i = 0; i < 1000; i++) { + const packagePath = resolvePath(path, 'package.json'); + const exists = await fs.pathExists(packagePath); + if (exists) { + return path; + } + + const newPath = dirname(path); + if (newPath === path) { + return undefined; + } + path = newPath; + } + + throw new Error( + `Iteration limit reached when searching for root package.json at ${searchDir}`, + ); +} + +/** @internal */ +export class PackageDiscoveryService implements FeatureDiscoveryService { + constructor( + private readonly config: RootConfigService, + private readonly logger: RootLoggerService, + ) {} + + getDependencyNames(path: string) { + const { dependencies } = require(path) as BackstagePackageJson; + const packagesConfig = this.config.getOptional('backend.packages'); + + const dependencyNames = Object.keys(dependencies || {}); + + if (packagesConfig === 'all') { + return dependencyNames; + } + + const includedPackagesConfig = this.config.getOptionalStringArray( + 'backend.packages.include', + ); + + const includedPackages = includedPackagesConfig + ? new Set(includedPackagesConfig) + : dependencyNames; + const excludedPackagesSet = new Set( + this.config.getOptionalStringArray('backend.packages.exclude'), + ); + + return [...includedPackages].filter(name => !excludedPackagesSet.has(name)); + } + + async getBackendFeatures(): Promise<{ features: Array }> { + const packagesConfig = this.config.getOptional('backend.packages'); + if (!packagesConfig || Object.keys(packagesConfig).length === 0) { + return { features: [] }; + } + + const packageDir = await findClosestPackageDir(process.argv[1]); + if (!packageDir) { + throw new Error('Package discovery failed to find package.json'); + } + const dependencyNames = this.getDependencyNames( + resolvePath(packageDir, 'package.json'), + ); + + const features: BackendFeature[] = []; + + for (const name of dependencyNames) { + const depPkg = require(require.resolve(`${name}/package.json`, { + paths: [packageDir], + })) as BackstagePackageJson; + if ( + !depPkg?.backstage?.role || + !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role) + ) { + continue; // Not a backstage backend package, ignore + } + + const exportedModulePaths = [ + require.resolve(name, { + paths: [packageDir], + }), + ]; + + // Find modules exported as alpha + try { + exportedModulePaths.push( + require.resolve(`${name}/alpha`, { paths: [packageDir] }), + ); + } catch { + /* ignore */ + } + + for (const modulePath of exportedModulePaths) { + const mod = require(modulePath); + + if (isBackendFeature(mod.default)) { + this.logger.info(`Detected: ${name}`); + features.push(mod.default); + } + if (isBackendFeatureFactory(mod.default)) { + this.logger.info(`Detected: ${name}`); + features.push(mod.default()); + } + } + } + + return { features }; + } +} diff --git a/packages/backend-defaults/src/discoveryFeatureLoader.ts b/packages/backend-defaults/src/discoveryFeatureLoader.ts new file mode 100644 index 0000000000..dc3c9c1d90 --- /dev/null +++ b/packages/backend-defaults/src/discoveryFeatureLoader.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createBackendFeatureLoader, +} from '@backstage/backend-plugin-api'; +import { PackageDiscoveryService } from './PackageDiscoveryService'; + +/** + * A loader that discovers backend features from the current package.json and its dependencies. + * + * @public + * + * @example + * Using the `discoveryFeatureLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { discoveryFeatureLoader } from '@backstage/backend-defaults'; + * + * const backend = createBackend(); + * backend.add(discoveryFeatureLoader); + * //... + * backend.start(); + * ``` + */ +export const discoveryFeatureLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + async loader({ config, logger }) { + const service = new PackageDiscoveryService(config, logger); + const { features } = await service.getBackendFeatures(); + return features; + }, +}); 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 f9ef4c9762..cb672ed7f1 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts @@ -19,25 +19,18 @@ import { mockServices, 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: { @@ -74,12 +67,16 @@ describe('authServiceFactory', () => { 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, { @@ -94,21 +91,15 @@ describe('authServiceFactory', () => { 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.]`, ); }); @@ -151,38 +142,7 @@ describe('authServiceFactory', () => { ); }); - it('should forward user token 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.getSubject('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 () => { + 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', @@ -197,22 +157,14 @@ describe('authServiceFactory', () => { 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', }), ); }); @@ -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 () => { 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/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/cache/CacheClient.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts index aba62558d4..f519daa6df 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts @@ -22,6 +22,7 @@ import { import { JsonValue } from '@backstage/types'; import { createHash } from 'crypto'; import Keyv from 'keyv'; +import { ttlToMilliseconds } from './types'; export type CacheClientFactory = (options: CacheServiceOptions) => Keyv; @@ -58,7 +59,9 @@ export class DefaultCacheClient implements CacheService { opts: CacheServiceSetOptions = {}, ): Promise { const k = this.getNormalizedKey(key); - await this.#client.set(k, value, opts.ttl); + const ttl = + opts.ttl !== undefined ? ttlToMilliseconds(opts.ttl) : undefined; + await this.#client.set(k, value, ttl); } async delete(key: string): Promise { diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts index 6eb907764d..997739faa2 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts @@ -52,10 +52,10 @@ describe('CacheManager integration', () => { }), ); - manager.forPlugin('p1').getClient(); - manager.forPlugin('p1').getClient({ defaultTtl: 200 }); - manager.forPlugin('p2').getClient(); - manager.forPlugin('p3').getClient({}); + manager.forPlugin('p1'); + manager.forPlugin('p1').withOptions({ defaultTtl: 200 }); + manager.forPlugin('p2'); + manager.forPlugin('p3').withOptions({}); if (store === 'redis') { // eslint-disable-next-line jest/no-conditional-expect @@ -80,9 +80,11 @@ describe('CacheManager integration', () => { }), ); - const plugin1 = manager.forPlugin('p1').getClient(); - const plugin2a = manager.forPlugin('p2').getClient(); - const plugin2b = manager.forPlugin('p2').getClient({ defaultTtl: 2000 }); + const plugin1 = manager.forPlugin('p1'); + const plugin2a = manager.forPlugin('p2'); + const plugin2b = manager + .forPlugin('p2') + .withOptions({ defaultTtl: 2000 }); await plugin1.set('a', 'plugin1'); await plugin2a.set('a', 'plugin2a'); @@ -93,4 +95,96 @@ describe('CacheManager integration', () => { await expect(plugin2b.get('a')).resolves.toBe('plugin2b'); }, ); + + it.each(caches.eachSupportedId())( + 'supports both milliseconds and human durations throughout, %p', + async cacheId => { + const { store, connection } = await caches.init(cacheId); + + for (const defaultTtl of [200, { milliseconds: 200 }]) { + const manager = CacheManager.fromConfig( + mockServices.rootConfig({ + data: { + backend: { + cache: { + store, + connection, + defaultTtl, + }, + }, + }, + }), + ).forPlugin('p'); + + const defaultClient = manager; + const numberOverrideClient = manager.withOptions({ defaultTtl: 400 }); + const durationOverrideClient = manager.withOptions({ + defaultTtl: { milliseconds: 400 }, + }); + + await defaultClient.set('a', 'x'); + await defaultClient.set('b', 'x'); + await numberOverrideClient.set('c', 'x'); + await durationOverrideClient.set('d', 'x'); + await defaultClient.set('e', 'x', { ttl: 400 }); + await defaultClient.set('f', 'x', { ttl: { milliseconds: 400 } }); + + await expect(defaultClient.get('a')).resolves.toBe('x'); + await expect(defaultClient.get('b')).resolves.toBe('x'); + await expect(defaultClient.get('c')).resolves.toBe('x'); + await expect(defaultClient.get('d')).resolves.toBe('x'); + await expect(defaultClient.get('e')).resolves.toBe('x'); + await expect(defaultClient.get('f')).resolves.toBe('x'); + + await new Promise(resolve => setTimeout(resolve, 50 + 200)); + + await expect(defaultClient.get('a')).resolves.toBeUndefined(); + await expect(defaultClient.get('b')).resolves.toBeUndefined(); + await expect(defaultClient.get('c')).resolves.toBe('x'); + await expect(defaultClient.get('d')).resolves.toBe('x'); + await expect(defaultClient.get('e')).resolves.toBe('x'); + await expect(defaultClient.get('f')).resolves.toBe('x'); + + await new Promise(resolve => setTimeout(resolve, 200)); + + await expect(defaultClient.get('a')).resolves.toBeUndefined(); + await expect(defaultClient.get('b')).resolves.toBeUndefined(); + await expect(defaultClient.get('c')).resolves.toBeUndefined(); + await expect(defaultClient.get('d')).resolves.toBeUndefined(); + await expect(defaultClient.get('e')).resolves.toBeUndefined(); + await expect(defaultClient.get('f')).resolves.toBeUndefined(); + } + }, + ); + + it('rejects invalid defaultTtl', () => { + expect(() => + CacheManager.fromConfig( + mockServices.rootConfig({ + data: { + backend: { + cache: { + store: 'memory', + }, + }, + }, + }), + ), + ).not.toThrow(); + + expect(() => + CacheManager.fromConfig( + mockServices.rootConfig({ + data: { + backend: { + cache: { + store: 'memory', + defaultTtl: 'hello', + }, + }, + }, + }), + ), + ).toThrow(/Invalid configuration backend.cache.defaultTtl/); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 873bcf9648..41fe595123 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -15,13 +15,15 @@ */ import { + CacheService, CacheServiceOptions, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import Keyv from 'keyv'; import { DefaultCacheClient } from './CacheClient'; -import { CacheManagerOptions, PluginCacheManager } from './types'; +import { CacheManagerOptions, ttlToMilliseconds } from './types'; +import { durationToMilliseconds } from '@backstage/types'; type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; @@ -57,13 +59,13 @@ export class CacheManager { * @param config - The loaded application configuration. */ static fromConfig( - config: Config, + config: RootConfigService, options: CacheManagerOptions = {}, ): CacheManager { // If no `backend.cache` config is provided, instantiate the CacheManager // with an in-memory cache client. const store = config.getOptionalString('backend.cache.store') || 'memory'; - const defaultTtl = config.getOptionalNumber('backend.cache.defaultTtl'); + const defaultTtlConfig = config.getOptional('backend.cache.defaultTtl'); const connectionString = config.getOptionalString('backend.cache.connection') || ''; const useRedisSets = @@ -71,6 +73,23 @@ export class CacheManager { const logger = options.logger?.child({ type: 'cacheManager', }); + + let defaultTtl: number | undefined; + if (defaultTtlConfig !== undefined && defaultTtlConfig !== null) { + if (typeof defaultTtlConfig === 'number') { + defaultTtl = defaultTtlConfig; + } else if ( + typeof defaultTtlConfig === 'object' && + !Array.isArray(defaultTtlConfig) + ) { + defaultTtl = durationToMilliseconds(defaultTtlConfig); + } else { + throw new Error( + `Invalid configuration backend.cache.defaultTtl: ${defaultTtlConfig}, expected milliseconds number or HumanDuration object`, + ); + } + } + return new CacheManager( store, connectionString, @@ -107,23 +126,16 @@ export class CacheManager { * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ - forPlugin(pluginId: string): PluginCacheManager { - return { - getClient: (defaultOptions = {}) => { - const clientFactory = (options: CacheServiceOptions) => { - return this.getClientWithTtl( - pluginId, - options.defaultTtl ?? this.defaultTtl, - ); - }; - - return new DefaultCacheClient( - clientFactory(defaultOptions), - clientFactory, - defaultOptions, - ); - }, + forPlugin(pluginId: string): CacheService { + const clientFactory = (options: CacheServiceOptions) => { + const ttl = options.defaultTtl ?? this.defaultTtl; + return this.getClientWithTtl( + pluginId, + ttl !== undefined ? ttlToMilliseconds(ttl) : undefined, + ); }; + + return new DefaultCacheClient(clientFactory({}), clientFactory, {}); } private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv { diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts index f16b306d81..352cde44d7 100644 --- a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -40,6 +40,6 @@ export const cacheServiceFactory = createServiceFactory({ return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { - return manager.forPlugin(plugin.getId()).getClient(); + return manager.forPlugin(plugin.getId()); }, }); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts index b16fa56bd2..958fa36091 100644 --- a/packages/backend-defaults/src/entrypoints/cache/index.ts +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -16,4 +16,4 @@ export { cacheServiceFactory } from './cacheServiceFactory'; export { CacheManager } from './CacheManager'; -export type { CacheManagerOptions, PluginCacheManager } from './types'; +export type { CacheManagerOptions } from './types'; diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts index ccb3ed5f6d..7260099a0a 100644 --- a/packages/backend-defaults/src/entrypoints/cache/types.ts +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -15,10 +15,7 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { - CacheService, - CacheServiceOptions, -} from '@backstage/backend-plugin-api'; +import { HumanDuration, durationToMilliseconds } from '@backstage/types'; /** * Options given when constructing a {@link CacheManager}. @@ -38,9 +35,6 @@ export type CacheManagerOptions = { onError?: (err: Error) => void; }; -/** - * @public - */ -export interface PluginCacheManager { - getClient(options?: CacheServiceOptions): CacheService; +export function ttlToMilliseconds(ttl: number | HumanDuration): number { + return typeof ttl === 'number' ? ttl : durationToMilliseconds(ttl); } diff --git a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts index 347ec742c0..26c2d54231 100644 --- a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts @@ -17,20 +17,24 @@ import { ConfigReader } from '@backstage/config'; import { DatabaseManagerImpl } from './DatabaseManager'; import { Connector } from './types'; +import { mockServices } from '@backstage/backend-test-utils'; describe('DatabaseManagerImpl', () => { afterEach(() => { jest.clearAllMocks(); }); + const deps = { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }; + it('calls the right connector, only once per plugin id', async () => { const connector1 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const connector2 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -43,30 +47,28 @@ describe('DatabaseManagerImpl', () => { }, ); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin2').getClient(); + await impl.forPlugin('plugin2', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(2); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); }); it('respects per-plugin overridden connectors', async () => { const connector1 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const connector2 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -84,39 +86,93 @@ describe('DatabaseManagerImpl', () => { }, ); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin2').getClient(); + await impl.forPlugin('plugin2', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(1); - expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', deps); }); - it('retains the migration skip info', async () => { + it('migration skip options take precedence over config', async () => { const connector = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; + const impl = new DatabaseManagerImpl( + new ConfigReader({ + client: 'pg', + backend: { + database: { + skipMigrations: true, + plugin: { plugin1: { skipMigrations: true } }, + }, + }, + }), + { + pg: connector, + }, + { migrations: { skip: false } }, + ); + expect((await impl.forPlugin('plugin1', deps)).migrations).toEqual({ + skip: false, + }); + const impl1 = new DatabaseManagerImpl(new ConfigReader({ client: 'pg' }), { pg: connector, }); - const impl2 = new DatabaseManagerImpl( - new ConfigReader({ client: 'pg' }), - { pg: connector }, - { migrations: { skip: true } }, + expect((await impl1.forPlugin('plugin1', deps)).migrations).toEqual({ + skip: false, + }); + }); + + it('plugin can skip migrations using config', async () => { + const connector = { + getClient: jest.fn(), + } satisfies Connector; + + const impl = new DatabaseManagerImpl( + new ConfigReader({ + client: 'pg', + backend: { + database: { plugin: { plugin1: { skipMigrations: true } } }, + }, + }), + { + pg: connector, + }, ); - expect((await impl1.forPlugin('plugin1')).migrations).toEqual({ + expect((await impl.forPlugin('plugin1', deps)).migrations).toEqual({ + skip: true, + }); + expect((await impl.forPlugin('plugin2', deps)).migrations).toEqual({ skip: false, }); - expect((await impl2.forPlugin('plugin1')).migrations).toEqual({ + const impl2 = new DatabaseManagerImpl( + new ConfigReader({ + client: 'pg', + backend: { + database: { + skipMigrations: true, + plugin: { plugin1: { skipMigrations: false } }, + }, + }, + }), + { + pg: connector, + }, + ); + expect((await impl2.forPlugin('plugin1', deps)).migrations).toEqual({ + skip: false, + }); + expect((await impl2.forPlugin('plugin2', deps)).migrations).toEqual({ skip: true, }); }); diff --git a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts index 7e3adf17df..46d4ee8cf7 100644 --- a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts @@ -18,7 +18,7 @@ import { DatabaseService, LifecycleService, LoggerService, - PluginMetadataService, + RootConfigService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { stringifyError } from '@backstage/errors'; @@ -42,21 +42,12 @@ function pluginPath(pluginId: string): string { */ export type DatabaseManagerOptions = { migrations?: DatabaseService['migrations']; - logger?: LoggerService; -}; - -/** - * An interface that represents the legacy global DatabaseManager implementation. - * @public - */ -export type LegacyRootDatabaseService = { - forPlugin(pluginId: string): DatabaseService; }; /** * Testable implementation class for {@link DatabaseManager} below. */ -export class DatabaseManagerImpl implements LegacyRootDatabaseService { +export class DatabaseManagerImpl { constructor( private readonly config: Config, private readonly connectors: Record, @@ -73,9 +64,9 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { */ forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): PluginDatabaseManager { const client = this.getClientType(pluginId).client; @@ -86,8 +77,16 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { ); } const getClient = () => this.getDatabase(pluginId, connector, deps); - const migrations = { skip: false, ...this.options?.migrations }; - return { getClient, migrations }; + + const skip = + this.options?.migrations?.skip ?? + this.config.getOptionalBoolean( + `backend.database.plugin.${pluginId}.skipMigrations`, + ) ?? + this.config.getOptionalBoolean('backend.database.skipMigrations') ?? + false; + + return { getClient, migrations: { skip } }; } /** @@ -127,9 +126,9 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { private async getDatabase( pluginId: string, connector: Connector, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { if (this.databaseCache.has(pluginId)) { @@ -140,13 +139,19 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { this.databaseCache.set(pluginId, clientPromise); if (process.env.NODE_ENV !== 'test') { - clientPromise.then(client => this.startKeepaliveLoop(pluginId, client)); + clientPromise.then(client => + this.startKeepaliveLoop(pluginId, client, deps.logger), + ); } return clientPromise; } - private startKeepaliveLoop(pluginId: string, client: Knex): void { + private startKeepaliveLoop( + pluginId: string, + client: Knex, + logger: LoggerService, + ): void { let lastKeepaliveFailed = false; setInterval(() => { @@ -159,7 +164,7 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { (error: unknown) => { if (!lastKeepaliveFailed) { lastKeepaliveFailed = true; - this.options?.logger?.warn( + logger.warn( `Database keepalive failed for plugin ${pluginId}, ${stringifyError( error, )}`, @@ -184,7 +189,7 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { * set `prefix` which is used to prefix generated database names if config is * not provided. */ -export class DatabaseManager implements LegacyRootDatabaseService { +export class DatabaseManager { /** * Creates a {@link DatabaseManager} from `backend.database` config. * @@ -192,7 +197,7 @@ export class DatabaseManager implements LegacyRootDatabaseService { * @param options - An optional configuration object. */ static fromConfig( - config: Config, + config: RootConfigService, options?: DatabaseManagerOptions, ): DatabaseManager { const databaseConfig = config.getConfig('backend.database'); @@ -224,31 +229,11 @@ export class DatabaseManager implements LegacyRootDatabaseService { */ forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): PluginDatabaseManager { return this.impl.forPlugin(pluginId, deps); } } - -/** - * Helper for deleting databases. - * - * @public - * @deprecated Will be removed in a future release. - */ -export async function dropDatabase( - dbConfig: Config, - ...databaseNames: string[] -): Promise { - const client = dbConfig.getString('client'); - const prefix = dbConfig.getOptionalString('prefix') || 'backstage_plugin_'; - - if (client === 'pg') { - await new PgConnector(dbConfig, prefix).dropDatabase(...databaseNames); - } else if (client === 'mysql' || client === 'mysql2') { - await new MysqlConnector(dbConfig, prefix).dropDatabase(...databaseNames); - } -} diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts index b11b659a6d..d5bd88deef 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; @@ -253,9 +250,9 @@ export class MysqlConnector implements Connector { async getClient( pluginId: string, - _deps?: { + _deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -293,10 +290,6 @@ export class MysqlConnector implements Connector { return client; } - async dropDatabase(...databaseNames: string[]): Promise { - return await dropMysqlDatabase(this.config, ...databaseNames); - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 444bbc0d18..b5cfffd1f4 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; @@ -29,6 +26,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 +50,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); }, ); } @@ -259,9 +258,9 @@ export class PgConnector implements Connector { async getClient( pluginId: string, - _deps?: { + _deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -310,10 +309,6 @@ export class PgConnector implements Connector { return client; } - async dropDatabase(...databaseNames: string[]): Promise { - return await dropPgDatabase(this.config, ...databaseNames); - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts index 2bd517d373..769d402eda 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts @@ -20,11 +20,17 @@ import { buildSqliteDatabaseConfig, createSqliteDatabaseClient, } from './sqlite3'; +import { mockServices } from '@backstage/backend-test-utils'; describe('better-sqlite3', () => { const createConfig = (connection: any) => new ConfigReader({ client: 'better-sqlite3', connection }); + const deps = { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }; + describe('buildSqliteDatabaseConfig', () => { it('builds an in-memory connection', () => { expect(buildSqliteDatabaseConfig(createConfig(':memory:'))).toEqual({ @@ -90,7 +96,9 @@ describe('better-sqlite3', () => { describe('createSqliteDatabaseClient', () => { it('creates an in memory knex instance', () => { - expect(createSqliteDatabaseClient(createConfig(':memory:'))).toBeTruthy(); + expect( + createSqliteDatabaseClient('p', createConfig(':memory:'), deps), + ).toBeTruthy(); }); }); }); diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts index 97ba6d46fd..67dd9d975f 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts @@ -15,10 +15,7 @@ */ import { DevDataStore } from '@backstage/backend-dev-utils'; -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ensureDirSync } from 'fs-extra'; @@ -35,12 +32,13 @@ import { mergeDatabaseConfig } from './mergeDatabaseConfig'; * @param overrides - Additional options to merge with the config */ export function createSqliteDatabaseClient( + pluginId: string, dbConfig: Config, - overrides?: Knex.Config, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, + overrides?: Knex.Config, ) { const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides); const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig; @@ -62,7 +60,7 @@ export function createSqliteDatabaseClient( const devStore = DevDataStore.get(); if (devStore) { - const dataKey = `sqlite3-db-${deps.pluginMetadata.getId()}`; + const dataKey = `sqlite3-db-${pluginId}`; const connectionLoader = async () => { // If seed data is available, use it tconnectionLoader restore the database @@ -180,9 +178,9 @@ export class Sqlite3Connector implements Connector { async getClient( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -202,18 +200,15 @@ export class Sqlite3Connector implements Connector { ); const client = createSqliteDatabaseClient( + pluginId, pluginConfig, - databaseClientOverrides, deps, + databaseClientOverrides, ); return client; } - async dropDatabase(..._databaseNames: string[]): Promise { - // do nothing - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts index 3bdf1790f3..167779e807 100644 --- a/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts @@ -35,6 +35,7 @@ export const databaseServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, lifecycle: coreServices.lifecycle, + logger: coreServices.logger, pluginMetadata: coreServices.pluginMetadata, }, async createRootContext({ config }) { @@ -48,10 +49,10 @@ export const databaseServiceFactory = createServiceFactory({ }), ); }, - async factory({ pluginMetadata, lifecycle }, databaseManager) { + async factory({ pluginMetadata, lifecycle, logger }, databaseManager) { return databaseManager.forPlugin(pluginMetadata.getId(), { - pluginMetadata, lifecycle, + logger, }); }, }); diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts index 7d6221b856..e30d06cbdd 100644 --- a/packages/backend-defaults/src/entrypoints/database/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -18,6 +18,4 @@ export { databaseServiceFactory } from './databaseServiceFactory'; export { DatabaseManager, type DatabaseManagerOptions, - type LegacyRootDatabaseService, - dropDatabase, } from './DatabaseManager'; diff --git a/packages/backend-defaults/src/entrypoints/database/types.ts b/packages/backend-defaults/src/entrypoints/database/types.ts index a9cceaa1f9..beded85d65 100644 --- a/packages/backend-defaults/src/entrypoints/database/types.ts +++ b/packages/backend-defaults/src/entrypoints/database/types.ts @@ -14,87 +14,17 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; -/** - * Manages an underlying Knex database driver. - */ -export interface DatabaseConnector { - /** - * Provides an instance of a knex database connector. - */ - createClient( - dbConfig: Config, - overrides?: Partial, - deps?: { - lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; - }, - ): Knex; - - /** - * Provides a partial knex config sufficient to override a database name. - */ - createNameOverride(name: string): Partial; - - /** - * Provides a partial knex config sufficient to override a PostgreSQL schema - * name within utilizing the `searchPath` knex configuration. - */ - createSchemaOverride?(name: string): Partial; - - /** - * Produces a knex connection config object representing a database connection - * string. - */ - parseConnectionString( - connectionString: string, - client?: string, - ): Knex.StaticConnectionConfig; - - /** - * Performs a side-effect to ensure database names passed in are present. - * - * Calling this function on databases which already exist should do nothing. - * Missing databases should be created if needed. - */ - ensureDatabaseExists?( - dbConfig: Config, - ...databases: Array - ): Promise; - - /** - * Performs a side-effect to ensure schema names passed in are present. - * - * Calling this function on schemas which already exist should do nothing. - * Missing schemas should be created if needed. - */ - ensureSchemaExists?( - dbConfig: Config, - ...schemas: Array - ): Promise; - - /** - * Deletes databases. - */ - dropDatabase?(dbConfig: Config, ...databases: Array): Promise; -} - export interface Connector { getClient( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise; - - dropDatabase(...databaseNames: string[]): Promise; } diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts index 4e6aff5853..e4e30044dd 100644 --- a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts @@ -54,25 +54,6 @@ describe('HostDiscovery', () => { ); }); - 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'], diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts index 559dda2aa0..47793c82f7 100644 --- a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts @@ -15,7 +15,10 @@ */ import { Config } from '@backstage/config'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { + DiscoveryService, + RootConfigService, +} from '@backstage/backend-plugin-api'; import { readHttpServerOptions } from '../rootHttpRouter/http/config'; type Target = string | { internal: string; external: string }; @@ -39,6 +42,7 @@ export class HostDiscovery implements DiscoveryService { * * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`. * eg. + * * ```yaml * discovery: * endpoints: @@ -52,11 +56,11 @@ export class HostDiscovery implements DiscoveryService { * plugins: [search] * ``` * - * The basePath defaults to `/api`, meaning the default full internal + * The fixed base path is `/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 }) { - const basePath = options?.basePath ?? '/api'; + static fromConfig(config: RootConfigService) { + const basePath = '/api'; const externalBaseUrl = config .getString('backend.baseUrl') .replace(/\/+$/, ''); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.test.ts b/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.test.ts index 124acf277b..22c6c90f05 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.test.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.test.ts @@ -19,13 +19,11 @@ import express from 'express'; import request from 'supertest'; import { createCredentialsBarrier } from './createCredentialsBarrier'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -import { MiddlewareFactory } from '../rootHttpRouter/http'; - -const errorMiddleware = MiddlewareFactory.create({ - config: mockServices.rootConfig(), - logger: mockServices.rootLogger(), -}).error(); +import { + mockCredentials, + mockErrorHandler, + mockServices, +} from '@backstage/backend-test-utils'; function setup() { const barrier = createCredentialsBarrier({ @@ -37,7 +35,7 @@ function setup() { const app = express(); app.use(barrier.middleware); - app.use(errorMiddleware); + app.use(mockErrorHandler()); app.get('*', (_req, res) => res.status(200).end()); return { app, barrier }; diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts index a8b749939b..3c721c9ed9 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts @@ -110,7 +110,7 @@ describe('httpRouterFactory', () => { }); const defaultServices = [ - httpRouterServiceFactory(), + httpRouterServiceFactory, mockServices.httpAuth.factory({ defaultCredentials: mockCredentials.none(), }), 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 63a7353241..ab31ec4850 100644 --- a/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts @@ -60,7 +60,7 @@ export const rootConfigServiceFactoryWithOptions = ( console.log(`Loading config from ${source}`); return await ConfigSources.toConfig(source); }, - })(); + }); /** * @public 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/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index c3a97737e4..a18ddbe096 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -123,7 +123,7 @@ const rootHttpRouterServiceFactoryWithOptions = ( return router; }, - })(); + }); /** @public */ export const rootHttpRouterServiceFactory = Object.assign( diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts index f8d06baf56..5c3af24ab1 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts @@ -23,13 +23,15 @@ import { SchedulerServiceTaskRunner, SchedulerServiceTaskScheduleDefinition, } from '@backstage/backend-plugin-api'; -import { Counter, Histogram, metrics } from '@opentelemetry/api'; +import { Counter, Histogram, metrics, trace } from '@opentelemetry/api'; import { Knex } from 'knex'; import { Duration } from 'luxon'; import { LocalTaskWorker } from './LocalTaskWorker'; import { TaskWorker } from './TaskWorker'; import { TaskSettingsV2 } from './types'; -import { validateId } from './util'; +import { TRACER_ID, validateId } from './util'; + +const tracer = trace.getTracer(TRACER_ID); /** * Implements the actual task management. @@ -85,7 +87,7 @@ export class PluginTaskSchedulerImpl implements SchedulerService { const knex = await this.databaseFactory(); const worker = new TaskWorker( task.id, - this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), + this.instrumentedFunction(task, scope), knex, this.logger.child({ task: task.id }), ); @@ -93,7 +95,7 @@ export class PluginTaskSchedulerImpl implements SchedulerService { } else { const worker = new LocalTaskWorker( task.id, - this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), + this.instrumentedFunction(task, scope), this.logger.child({ task: task.id }), ); worker.start(settings, { signal: task.signal }); @@ -121,20 +123,33 @@ export class PluginTaskSchedulerImpl implements SchedulerService { return this.allScheduledTasks; } - private wrapInMetrics( - fn: SchedulerServiceTaskFunction, - opts: { labels: Record }, + private instrumentedFunction( + task: SchedulerServiceTaskInvocationDefinition, + scope: string, ): SchedulerServiceTaskFunction { return async abort => { - const labels = { - ...opts.labels, + const labels: Record = { + taskId: task.id, + scope, }; this.counter.add(1, { ...labels, result: 'started' }); const startTime = process.hrtime(); try { - await fn(abort); + await tracer.startActiveSpan(`task ${task.id}`, async span => { + try { + span.setAttributes(labels); + await task.fn(abort); + } catch (error) { + if (error instanceof Error) { + span.recordException(error); + } + throw error; + } finally { + span.end(); + } + }); labels.result = 'completed'; } catch (ex) { labels.result = 'failed'; diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts index 70d67a9fbe..c475a20b80 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts @@ -18,6 +18,8 @@ import { InputError } from '@backstage/errors'; import { Knex } from 'knex'; import { DateTime, Duration } from 'luxon'; +export const TRACER_ID = 'backstage-service-scheduler'; + // Keep the IDs compatible with e.g. Prometheus labels export function validateId(id: string) { if (typeof id !== 'string' || !id.trim()) { diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts index b16509cf96..2d84f6ce87 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts @@ -20,7 +20,7 @@ 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.getSubject(); await scheduler.scheduleTask({ diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts index cb496dd702..96e9ed6509 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { + LoggerService, + RootConfigService, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ReaderFactory } from './types'; import { UrlReaderPredicateMux } from './UrlReaderPredicateMux'; import { AzureUrlReader } from './AzureUrlReader'; @@ -40,7 +43,7 @@ import { HarnessUrlReader } from './HarnessUrlReader'; */ export type UrlReadersOptions = { /** Root config object */ - config: Config; + config: RootConfigService; /** Logger used by all the readers */ logger: LoggerService; /** A list of factories used to construct individual readers that match on URLs */ diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts index 510b12545f..5afbed178f 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts @@ -15,11 +15,11 @@ */ import { Readable } from 'stream'; -import { Config } from '@backstage/config'; import { UrlReaderService, UrlReaderServiceReadTreeResponse, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; /** @@ -40,7 +40,7 @@ export type UrlReaderPredicateTuple = { * @public */ export type ReaderFactory = (options: { - config: Config; + config: RootConfigService; logger: LoggerService; treeResponseFactory: ReadTreeResponseFactory; }) => UrlReaderPredicateTuple[]; diff --git a/packages/backend-defaults/src/index.ts b/packages/backend-defaults/src/index.ts index b00c865524..5f26df8921 100644 --- a/packages/backend-defaults/src/index.ts +++ b/packages/backend-defaults/src/index.ts @@ -21,3 +21,4 @@ */ export { createBackend } from './CreateBackend'; +export { discoveryFeatureLoader } from './discoveryFeatureLoader'; 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 14111873a1..e76ed0b504 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,156 @@ # @backstage/backend-dynamic-feature-service +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## 0.4.0-next.0 + +### Minor Changes + +- 9080f57: **BREAKING**: `dynamicPluginsServiceFactory` is no longer callable as a function. If you need to provide options to make a custom factory, use `dynamicPluginsSchemasServiceFactoryWithOptions` instead. + +### Patch Changes + +- cd38da8: Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using the `dynamicPluginsFeatureDiscoveryLoader` to discover dynamic features in a new backend system. + + See usage examples below: + + Example using the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + //... + + const backend = createBackend(); + backend.add(dynamicPluginsFeatureDiscoveryLoader); + //... + backend.start(); + ``` + + Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + + ```ts + import { createBackend } from '@backstage/backend-defaults'; + import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + import { myCustomModuleLoader } from './myCustomModuleLoader'; + //... + + const backend = createBackend(); + backend.add( + dynamicPluginsFeatureDiscoveryLoader({ + moduleLoader: myCustomModuleLoader, + }), + ); + //... + backend.start(); + ``` + +- e27f889: Relax type check for a plugin's default export to also accept a BackendFeature defined as a function instead of an object +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-app-node@0.1.25-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-backend@0.3.12-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/plugin-search-backend-node@1.3.2-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 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + +## 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 diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 97dea60170..f6972da50b 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 { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +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 { @@ -113,23 +112,27 @@ export interface DynamicPluginsFactoryOptions { moduleLoader?(logger: LoggerService): ModuleLoader; } -// @public (undocumented) -export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactoryCompat< +// @public +export const dynamicPluginsFeatureDiscoveryLoader: (( + options?: DynamicPluginsFactoryOptions, +) => BackendFeature) & + BackendFeature; + +// @public @deprecated (undocumented) +export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, 'root', - 'singleton', - undefined + 'singleton' >; // @public (undocumented) -export const dynamicPluginsFrontendSchemas: BackendFeatureCompat; +export const dynamicPluginsFrontendSchemas: BackendFeature; // @public (undocumented) -export const dynamicPluginsRootLoggerServiceFactory: ServiceFactoryCompat< +export const dynamicPluginsRootLoggerServiceFactory: ServiceFactory< RootLoggerService, 'root', - 'singleton', - undefined + 'singleton' >; // @public (undocumented) @@ -146,22 +149,30 @@ export interface DynamicPluginsSchemasService { } // @public (undocumented) -export const dynamicPluginsSchemasServiceFactory: ServiceFactoryCompat< +export const dynamicPluginsSchemasServiceFactory: ServiceFactory< DynamicPluginsSchemasService, 'root', - 'singleton', - DynamicPluginsSchemasOptions + 'singleton' >; // @public (undocumented) -export const dynamicPluginsServiceFactory: ServiceFactoryCompat< +export const dynamicPluginsSchemasServiceFactoryWithOptions: ( + options?: DynamicPluginsSchemasOptions, +) => ServiceFactory; + +// @public @deprecated (undocumented) +export const dynamicPluginsServiceFactory: ServiceFactory< DynamicPluginProvider, 'root', - 'singleton', - DynamicPluginsFactoryOptions + 'singleton' >; -// @public (undocumented) +// @public @deprecated (undocumented) +export const dynamicPluginsServiceFactoryWithOptions: ( + options?: DynamicPluginsFactoryOptions, +) => ServiceFactory; + +// @public @deprecated (undocumented) export const dynamicPluginsServiceRef: ServiceRef< DynamicPluginProvider, 'root', @@ -210,7 +221,7 @@ export interface LegacyBackendPluginInstaller { // (undocumented) search?( indexBuilder: IndexBuilder, - schedule: TaskRunner, + schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment, ): void; } @@ -221,11 +232,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 d6ac21a3ae..451a373650 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.4.0-next.1", "description": "Backstage dynamic feature service", - "version": "0.2.16-next.2", - "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..d24a41a7a3 100644 --- a/packages/backend-dynamic-feature-service/src/manager/index.ts +++ b/packages/backend-dynamic-feature-service/src/manager/index.ts @@ -34,7 +34,9 @@ export { DynamicPluginManager, dynamicPluginsFeatureDiscoveryServiceFactory, dynamicPluginsServiceFactory, + dynamicPluginsServiceFactoryWithOptions, dynamicPluginsServiceRef, + dynamicPluginsFeatureDiscoveryLoader, } from './plugin-manager'; export type { 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..fef0b6000d 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -30,6 +30,7 @@ import { BackendFeature, LoggerService, coreServices, + createBackendFeatureLoader, createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; @@ -214,6 +215,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { /** * @public + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsServiceRef = createServiceRef( { @@ -231,9 +233,12 @@ export interface DynamicPluginsFactoryOptions { /** * @public + * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ -export const dynamicPluginsServiceFactory = createServiceFactory( - (options?: DynamicPluginsFactoryOptions) => ({ +export const dynamicPluginsServiceFactoryWithOptions = ( + options?: DynamicPluginsFactoryOptions, +) => + createServiceFactory({ service: dynamicPluginsServiceRef, deps: { config: coreServices.rootConfig, @@ -247,8 +252,14 @@ export const dynamicPluginsServiceFactory = createServiceFactory( moduleLoader: options?.moduleLoader?.(logger), }); }, - }), -); + }); + +/** + * @public + * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoader} instead. + */ +export const dynamicPluginsServiceFactory = + dynamicPluginsServiceFactoryWithOptions(); class DynamicPluginsEnabledFeatureDiscoveryService implements FeatureDiscoveryService @@ -285,6 +296,7 @@ class DynamicPluginsEnabledFeatureDiscoveryService /** * @public + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsFeatureDiscoveryServiceFactory = createServiceFactory({ @@ -298,10 +310,70 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory = }, }); +const dynamicPluginsFeatureDiscoveryLoaderWithOptions = ( + options?: DynamicPluginsFactoryOptions, +) => + createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + async loader({ config, logger }) { + const manager = await DynamicPluginManager.create({ + config, + logger, + preferAlpha: true, + moduleLoader: options?.moduleLoader?.(logger), + }); + const service = new DynamicPluginsEnabledFeatureDiscoveryService(manager); + const { features } = await service.getBackendFeatures(); + return features; + }, + }); + +/** + * A backend feature loader that uses the dynamic plugins system to discover features. + * + * @public + * + * @example + * Using the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + * + * const backend = createBackend(); + * backend.add(dynamicPluginsFeatureDiscoveryLoader); + * //... + * backend.start(); + * ``` + * + * @example + * Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + * import { myCustomModuleLoader } from './myCustomModuleLoader'; + * + * const backend = createBackend(); + * backend.add(dynamicPluginsFeatureDiscoveryLoader({ + * moduleLoader: myCustomModuleLoader + * })); + * //... + * backend.start(); + * ``` + */ +export const dynamicPluginsFeatureDiscoveryLoader = Object.assign( + dynamicPluginsFeatureDiscoveryLoaderWithOptions, + dynamicPluginsFeatureDiscoveryLoaderWithOptions(), +); + 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 a601890055..6a3068a37b 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,172 @@ # example-backend-legacy +## 0.2.102-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## 0.2.102-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.13-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend@1.5.17-next.0 + - @backstage/plugin-kubernetes-backend@0.18.6-next.0 + - @backstage/plugin-scaffolder-backend@1.25.0-next.0 + - @backstage/plugin-app-backend@0.3.74-next.0 + - @backstage/plugin-signals-backend@0.2.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-backend@0.3.12-next.0 + - @backstage/plugin-permission-backend@0.5.49-next.0 + - @backstage/plugin-proxy-backend@0.5.6-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.0 + - @backstage/plugin-devtools-backend@0.4.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-permission-common@0.8.1 + +## 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 diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index a650924720..65e5a46f28 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.101-next.2", + "version": "0.2.102-next.1", "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 682036d984..3d62a9d121 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/backend-openapi-utils +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/errors@1.2.4 + +## 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 diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 35db691c25..37ae412be6 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.16-next.2", + "version": "0.1.18-next.1", "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 d404d05b33..5fdf43aaa6 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,175 @@ # @backstage/backend-plugin-api +## 0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-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-permission-common@0.8.1 + +## 0.9.0-next.0 + +### Minor Changes + +- 19ff127: **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. +- f687050: Removed the following deprecated exports + + - `BackendPluginConfig` use `CreateBackendPluginOptions` + - `BackendModuleConfig` use `CreateBackendModuleOptions` + - `ExtensionPointConfig` use `CreateExtensionPointOptions` + +- 4d82481: Removed deprecated `ServiceFactoryOrFunction` type. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- cd38da8: Deprecate the `featureDiscoveryServiceRef` in favor of using the new `discoveryFeatureLoader` instead. +- 66dbf0a: Allow the cache service to accept the human duration format for TTL +- 0b2a402: Updates to the config schema to match reality +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-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-permission-common@0.8.1 + +## 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 diff --git a/packages/backend-plugin-api/api-report-alpha.md b/packages/backend-plugin-api/api-report-alpha.md index 0f4348336a..dea357502e 100644 --- a/packages/backend-plugin-api/api-report-alpha.md +++ b/packages/backend-plugin-api/api-report-alpha.md @@ -14,7 +14,7 @@ export interface FeatureDiscoveryService { }>; } -// @alpha +// @alpha @deprecated export const featureDiscoveryServiceRef: ServiceRef< FeatureDiscoveryService, 'root', diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 319ddf0bc6..89dfe40dd9 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -12,7 +12,6 @@ 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'; @@ -64,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) @@ -91,9 +81,6 @@ export interface BackendModuleRegistrationPoints { }): void; } -// @public @deprecated (undocumented) -export type BackendPluginConfig = CreateBackendPluginOptions; - // @public export interface BackendPluginRegistrationPoints { // (undocumented) @@ -175,12 +162,12 @@ export interface CacheService { // @public export type CacheServiceOptions = { - defaultTtl?: number; + defaultTtl?: number | HumanDuration; }; // @public export type CacheServiceSetOptions = { - ttl?: number; + ttl?: number | HumanDuration; }; // @public @@ -206,11 +193,7 @@ export namespace coreServices { const rootLifecycle: ServiceRef; const rootLogger: ServiceRef; const scheduler: ServiceRef; - const // @deprecated - tokenManager: ServiceRef; const urlReader: ServiceRef; - const // @deprecated - identity: ServiceRef; } // @public @@ -257,7 +240,7 @@ export interface CreateBackendFeatureLoaderOptions< // @public export function createBackendModule( options: CreateBackendModuleOptions, -): BackendFeatureCompat; +): BackendFeature; // @public export interface CreateBackendModuleOptions { @@ -270,7 +253,7 @@ export interface CreateBackendModuleOptions { // @public export function createBackendPlugin( options: CreateBackendPluginOptions, -): BackendFeatureCompat; +): BackendFeature; // @public export interface CreateBackendPluginOptions { @@ -297,25 +280,9 @@ export function createServiceFactory< TDeps extends { [name in string]: ServiceRef; }, - TOpts extends object | undefined = undefined, >( options: RootServiceFactoryOptions, -): ServiceFactoryCompat; - -// @public @deprecated -export function createServiceFactory< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, - TOpts extends object | undefined = undefined, ->( - options: ( - options?: TOpts, - ) => RootServiceFactoryOptions, -): ServiceFactoryCompat; +): ServiceFactory; // @public export function createServiceFactory< @@ -326,7 +293,6 @@ export function createServiceFactory< [name in string]: ServiceRef; }, TContext = undefined, - TOpts extends object | undefined = undefined, >( options: PluginServiceFactoryOptions< TService, @@ -335,29 +301,7 @@ export function createServiceFactory< TImpl, TDeps >, -): ServiceFactoryCompat; - -// @public @deprecated -export function createServiceFactory< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, - TContext = undefined, - TOpts extends object | undefined = undefined, ->( - options: ( - options?: TOpts, - ) => PluginServiceFactoryOptions< - TService, - TInstances, - TContext, - TImpl, - TDeps - >, -): ServiceFactoryCompat; +): ServiceFactory; // @public export function createServiceRef( @@ -401,9 +345,6 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; -// @public @deprecated (undocumented) -export type ExtensionPointConfig = CreateExtensionPointOptions; - // @public export interface HttpAuthService { credentials( @@ -437,9 +378,6 @@ export interface HttpRouterServiceAuthPolicy { path: string; } -// @public @deprecated -export interface IdentityService extends IdentityApi {} - export { isChildPath }; // @public @@ -511,17 +449,6 @@ export interface PluginMetadataService { getId(): string; } -// @public @deprecated (undocumented) -export type PluginServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TContext, - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, -> = PluginServiceFactoryOptions; - // @public (undocumented) export interface PluginServiceFactoryOptions< TService, @@ -553,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; @@ -604,19 +512,9 @@ export interface RootLifecycleService extends LifecycleService {} // @public export interface RootLoggerService extends LoggerService {} -// @public @deprecated (undocumented) -export type RootServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, -> = RootServiceFactoryOptions; - // @public (undocumented) 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? + TService, TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { @@ -707,15 +605,6 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { 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, @@ -726,22 +615,6 @@ export interface ServiceFactory< service: ServiceRef; } -// @public @deprecated (undocumented) -export interface ServiceFactoryCompat< - TService = unknown, - TScope extends 'plugin' | 'root' = 'plugin' | 'root', - TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', - TOpts extends object | undefined = undefined, -> extends ServiceFactory { - // @deprecated (undocumented) - ( - ...options: undefined extends TOpts ? [] : [options?: TOpts] - ): ServiceFactory; -} - -// @public @deprecated -export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); - // @public export type ServiceRef< TService, @@ -755,13 +628,6 @@ export type ServiceRef< $$type: '@backstage/ServiceRef'; }; -// @public @deprecated (undocumented) -export type ServiceRefConfig< - TService, - TScope extends 'root' | 'plugin', - TInstances extends 'singleton' | 'multiton', -> = ServiceRefOptions; - // @public (undocumented) export interface ServiceRefOptions< TService, @@ -772,10 +638,6 @@ export interface ServiceRefOptions< defaultFactory?( service: ServiceRef, ): Promise; - // @deprecated (undocumented) - defaultFactory?( - service: ServiceRef, - ): Promise<() => ServiceFactory>; // (undocumented) id: string; // (undocumented) @@ -784,14 +646,6 @@ export interface ServiceRefOptions< 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/config.d.ts b/packages/backend-plugin-api/config.d.ts new file mode 100644 index 0000000000..21122d7238 --- /dev/null +++ b/packages/backend-plugin-api/config.d.ts @@ -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. + */ + +export interface Config { + backend?: { + /** + * An absolute path to a directory that can be used as a working dir, for + * example as scratch space for large operations. + * + * @remarks + * + * Note that this must be an absolute path. + * + * If not set, the operating system's designated temporary directory is + * commonly used, but that is implementation defined per plugin. + * + * Plugins are encouraged to heed this config setting if present, to allow + * deployment in severely locked-down or limited environments. + */ + workingDirectory?: string; + }; +} diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 61f8b9353d..322c936b39 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.8.0-next.2", + "version": "0.9.0-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" @@ -40,7 +40,8 @@ } }, "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -67,5 +68,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" - } + }, + "configSchema": "config.d.ts" } diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha.ts index baee739f49..3f26c66fe9 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha.ts @@ -27,6 +27,7 @@ export interface FeatureDiscoveryService { /** * An optional service that can be used to dynamically load in additional BackendFeatures at runtime. * @alpha + * @deprecated The `featureDiscoveryServiceRef` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead. */ export const featureDiscoveryServiceRef = createServiceRef({ diff --git a/packages/backend-plugin-api/src/deprecated.ts b/packages/backend-plugin-api/src/deprecated.ts deleted file mode 100644 index 0d02f9ed7e..0000000000 --- a/packages/backend-plugin-api/src/deprecated.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ServiceRef, - ServiceRefOptions, - RootServiceFactoryOptions, - PluginServiceFactoryOptions, -} from './services'; - -/** - * @public - * @deprecated Use {@link ServiceRefOptions} instead - */ -export type ServiceRefConfig< - TService, - TScope extends 'root' | 'plugin', - TInstances extends 'singleton' | 'multiton', -> = ServiceRefOptions; - -/** - * @public - * @deprecated Use {@link RootServiceFactoryOptions} instead - */ -export type RootServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, -> = RootServiceFactoryOptions; - -/** - * @public - * @deprecated Use {@link PluginServiceFactoryOptions} instead - */ -export type PluginServiceFactoryConfig< - TService, - TInstances extends 'singleton' | 'multiton', - TContext, - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, -> = PluginServiceFactoryOptions; diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index eb554db25d..c2e2a13a89 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -21,7 +21,6 @@ */ export * from './services'; -export type { BackendFeature, BackendFeatureCompat } from './types'; +export type { BackendFeature } from './types'; export * from './paths'; export * from './wiring'; -export * from './deprecated'; diff --git a/packages/backend-plugin-api/src/services/definitions/CacheService.ts b/packages/backend-plugin-api/src/services/definitions/CacheService.ts index 3b13292a58..4c5cfd74b3 100644 --- a/packages/backend-plugin-api/src/services/definitions/CacheService.ts +++ b/packages/backend-plugin-api/src/services/definitions/CacheService.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/types'; +import { HumanDuration, JsonValue } from '@backstage/types'; /** * Options passed to {@link CacheService.set}. @@ -23,10 +23,10 @@ import { JsonValue } from '@backstage/types'; */ export type CacheServiceSetOptions = { /** - * Optional TTL in milliseconds. Defaults to the TTL provided when the client + * Optional TTL (in milliseconds if given as a number). Defaults to the TTL provided when the client * was set up (or no TTL if none are provided). */ - ttl?: number; + ttl?: number | HumanDuration; }; /** @@ -36,11 +36,11 @@ export type CacheServiceSetOptions = { */ export type CacheServiceOptions = { /** - * An optional default TTL (in milliseconds) to be set when getting a client + * An optional default TTL (in milliseconds if given as a number) to be set when getting a client * instance. If not provided, data will persist indefinitely by default (or * can be configured per entry at set-time). */ - defaultTtl?: number; + defaultTtl?: number | HumanDuration; }; /** 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/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 c2d3b9c6fc..82afa904fe 100644 --- a/packages/backend-plugin-api/src/services/system/index.ts +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -20,7 +20,5 @@ export type { ServiceFactory, PluginServiceFactoryOptions, RootServiceFactoryOptions, - ServiceFactoryOrFunction, - ServiceFactoryCompat, } 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 52322f998b..a98b8c44ee 100644 --- a/packages/backend-plugin-api/src/services/system/types.test.ts +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -23,15 +23,11 @@ import { 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() {}, @@ -39,52 +35,26 @@ describe('createServiceFactory', () => { return 'x'; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - expect(metaFactory().$$type).toBe('@backstage/BackendFeature'); - expect((metaFactory() as InternalServiceFactory).featureType).toBe( - 'service', - ); - 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() {}, @@ -92,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, @@ -183,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, @@ -251,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, @@ -280,61 +133,12 @@ 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(); - }); - - it('should create factory with options and dependencies', () => { - const metaFactory = createServiceFactory((_opts?: TestOptions) => ({ - service: ref, - deps: { - root: rootDep, - plugin: pluginDep, - }, - async createRootContext({ root }) { - const root1: number = root; - // @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); - 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'); }); it('should support old service refs without a multiton field', () => { const oldPluginDep = pluginDep as Omit; // Old refs don't have a multiton field - const metaFactory = createServiceFactory({ + const factory = createServiceFactory({ service: ref, deps: { plugin: oldPluginDep, @@ -347,82 +151,6 @@ describe('createServiceFactory', () => { return 'x'; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - }); - - 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 5bca592768..a14e388bd0 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -59,24 +59,6 @@ export interface ServiceFactory< service: ServiceRef; } -/** - * @public - * @deprecated This type exists only as a helper for old code that relied on `createServiceFactory` to return `() => ServiceFactory` instead of `ServiceFactory`. You should remove the `()` parentheses at the end of your usages. This type will be removed in a future release. - */ -export interface ServiceFactoryCompat< - TService = unknown, - TScope extends 'plugin' | 'root' = 'plugin' | 'root', - TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', - TOpts extends object | undefined = undefined, -> extends ServiceFactory { - /** - * @deprecated Callable service factories will be removed in a future release, please re-implement the service factory using the available APIs instead. If no options are being passed, you can simply remove the trailing `()`. - */ - ( - ...options: undefined extends TOpts ? [] : [options?: TOpts] - ): ServiceFactory; -} - /** @internal */ export interface InternalServiceFactory< TService = unknown, @@ -94,14 +76,6 @@ export interface InternalServiceFactory< ): Promise; } -/** - * Represents either a {@link ServiceFactory} or a function that returns one. - * - * @deprecated The support for service factory functions is deprecated and will be removed. - * @public - */ -export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); - /** @public */ export interface ServiceRefOptions< TService, @@ -114,12 +88,6 @@ export interface ServiceRefOptions< defaultFactory?( service: ServiceRef, ): Promise; - /** - * @deprecated The defaultFactory must return a plain `ServiceFactory` object, support for returning a function will be removed. - */ - defaultFactory?( - service: ServiceRef, - ): Promise<() => ServiceFactory>; } /** @@ -179,7 +147,7 @@ export function createServiceRef< } as ServiceRef & { __defaultFactory?: ( service: ServiceRef, - ) => Promise | (() => ServiceFactory)>; + ) => Promise>; }; } @@ -197,7 +165,7 @@ type ServiceRefsToInstances< /** @public */ 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? + TService, TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, @@ -259,31 +227,9 @@ export function createServiceFactory< TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, - TOpts extends object | undefined = undefined, >( options: RootServiceFactoryOptions, -): ServiceFactoryCompat; -/** - * Creates a root scoped service factory with optional options. - * - * @deprecated The ability to define options for service factories is deprecated - * and will be removed. Please use the non-callback form of createServiceFactory - * and provide an API that allows for a simple re-implementation of the service - * factory instead. - * @public - * @param 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, ->( - options: ( - options?: TOpts, - ) => RootServiceFactoryOptions, -): ServiceFactoryCompat; +): ServiceFactory; /** * Creates a plugin scoped service factory without options. * @@ -296,7 +242,6 @@ export function createServiceFactory< TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, TContext = undefined, - TOpts extends object | undefined = undefined, >( options: PluginServiceFactoryOptions< TService, @@ -305,100 +250,22 @@ export function createServiceFactory< TImpl, TDeps >, -): ServiceFactoryCompat; -/** - * Creates a plugin scoped service factory with optional options. - * - * @deprecated The ability to define options for service factories is deprecated - * and will be removed. Please use the non-callback form of createServiceFactory - * and provide an API that allows for a simple re-implementation of the service - * factory instead. - * @public - * @param 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, ->( - options: ( - options?: TOpts, - ) => PluginServiceFactoryOptions< - TService, - TInstances, - TContext, - TImpl, - TDeps - >, -): ServiceFactoryCompat; +): ServiceFactory; export function createServiceFactory< TService, TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, TContext, - TOpts extends object | undefined = undefined, >( options: | RootServiceFactoryOptions - | PluginServiceFactoryOptions - | (( - options: TOpts, - ) => RootServiceFactoryOptions) - | (( - options: TOpts, - ) => PluginServiceFactoryOptions< - TService, - TInstances, - TContext, - TImpl, - TDeps - >) - | (() => RootServiceFactoryOptions) - | (() => PluginServiceFactoryOptions< - TService, - TInstances, - TContext, - TImpl, - TDeps - >), -): ServiceFactoryCompat< - TService, - 'root' | 'plugin', - 'singleton' | 'multiton', - TOpts -> { - const configCallback = - typeof options === 'function' ? options : () => options; - const factory = ( - o?: TOpts, - ): InternalServiceFactory => { - const anyConf = configCallback(o!); - if (anyConf.service.scope === 'root') { - const c = anyConf as RootServiceFactoryOptions< - TService, - TInstances, - TImpl, - TDeps - >; - return { - $$type: '@backstage/BackendFeature', - version: 'v1', - featureType: 'service', - service: c.service, - initialization: c.initialization, - deps: c.deps, - factory: async (deps: ServiceRefsToInstances) => - c.factory(deps), - }; - } - const c = anyConf as PluginServiceFactoryOptions< + | PluginServiceFactoryOptions, +): ServiceFactory { + if (options.service.scope === 'root') { + const c = options as RootServiceFactoryOptions< TService, TInstances, - TContext, TImpl, TDeps >; @@ -408,23 +275,33 @@ export function createServiceFactory< featureType: 'service', service: c.service, initialization: c.initialization, - ...('createRootContext' in c - ? { - createRootContext: async ( - deps: ServiceRefsToInstances, - ) => c?.createRootContext?.(deps), - } - : {}), - deps: c.deps, - factory: async (deps: ServiceRefsToInstances, ctx: TContext) => - c.factory(deps, ctx), - }; - }; - - // This constructs the `ServiceFactoryCompat` type, which is both a plain - // factory object as well as a function that can be called to construct a - // factory, potentially with options. In the future only the plain factory - // form will be supported, but for now we need to allow callers to call the - // factory too. - return Object.assign(factory, factory(undefined as TOpts)); + 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 index 37fc079551..cf7ab18ace 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.test.ts @@ -46,18 +46,18 @@ describe('createBackendFeatureLoader', () => { createBackendPlugin({ pluginId: 'x', register() {}, - })(), + }), createServiceFactory({ service: coreServices.pluginMetadata, deps: {}, factory: () => ({ getId: () => 'fake-id' }), - })(), + }), // Dynamic import format Promise.resolve({ default: createBackendPlugin({ pluginId: 'y', register() {}, - })(), + }), }), ]; }, @@ -79,7 +79,7 @@ describe('createBackendFeatureLoader', () => { }); it('should support multiple output formats', async () => { - const feature = createBackendPlugin({ pluginId: 'x', register() {} })(); + const feature = createBackendPlugin({ pluginId: 'x', register() {} }); const dynamicFeature = Promise.resolve({ default: feature }); async function extractResult(f: BackendFeature) { diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts index 3426d5a90c..4d927c86f6 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts @@ -29,13 +29,6 @@ describe('createBackendModule', () => { }, }); - // legacy form - const legacy = result() as unknown as InternalBackendRegistrations; - 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 InternalBackendRegistrations; expect(module.$$type).toEqual('@backstage/BackendFeature'); expect(module.version).toEqual('v1'); @@ -52,9 +45,6 @@ describe('createBackendModule', () => { }, }, ]); - - // @ts-expect-error - expect(module({ a: 'a' })).toBeDefined(); }); it('should be able to depend on all types of dependencies', () => { diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.ts index 56f041606e..e2357a65d0 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendModule.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { BackendFeatureCompat } from '../types'; +import { BackendFeature } from '../types'; import { BackendModuleRegistrationPoints, InternalBackendModuleRegistration, InternalBackendPluginRegistration, + InternalBackendRegistrations, } from './types'; /** @@ -52,7 +53,7 @@ export interface CreateBackendModuleOptions { */ export function createBackendModule( options: CreateBackendModuleOptions, -): BackendFeatureCompat { +): BackendFeature { function getRegistrations() { const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = []; @@ -93,15 +94,10 @@ export function createBackendModule( ]; } - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { + return { $$type: '@backstage/BackendFeature' as const, + featureType: 'registrations', version: 'v1', getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; + } as InternalBackendRegistrations; } diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts index f4a0877b85..5acd8e46d7 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts @@ -28,13 +28,6 @@ describe('createBackendPlugin', () => { }, }); - // legacy form - const legacy = result() as unknown as InternalBackendRegistrations; - 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 InternalBackendRegistrations; expect(plugin.$$type).toEqual('@backstage/BackendFeature'); expect(plugin.version).toEqual('v1'); @@ -50,9 +43,6 @@ describe('createBackendPlugin', () => { }, }, ]); - - // @ts-expect-error - expect(plugin({ a: 'a' })).toBeDefined(); }); it('should be able to depend on all compatible dependencies', () => { diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts index f5984ef24b..185500c495 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts @@ -14,10 +14,11 @@ * limitations under the License. */ -import { BackendFeatureCompat } from '../types'; +import { BackendFeature } from '../types'; import { BackendPluginRegistrationPoints, InternalBackendPluginRegistration, + InternalBackendRegistrations, } from './types'; /** @@ -46,7 +47,7 @@ export interface CreateBackendPluginOptions { */ export function createBackendPlugin( options: CreateBackendPluginOptions, -): BackendFeatureCompat { +): BackendFeature { function getRegistrations() { const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = []; @@ -86,15 +87,10 @@ export function createBackendPlugin( ]; } - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { + return { $$type: '@backstage/BackendFeature' as const, version: 'v1', + featureType: 'registrations', getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; + } as InternalBackendRegistrations; } diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 95eea6c3d4..73b099978e 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -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-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md deleted file mode 100644 index 1a5879f14e..0000000000 --- a/packages/backend-tasks/CHANGELOG.md +++ /dev/null @@ -1,1400 +0,0 @@ -# @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 - -## 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 - -- 083eaf9: Fix bug where ISO durations could no longer be used for schedules -- Updated dependencies - - @backstage/backend-plugin-api@0.6.21-next.0 - - @backstage/backend-common@0.23.2-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.24 - -### Patch Changes - -- 736bc3c: Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api` and `@backstage/backend-defaults` -- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` -- 6a576dc: Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependency on the soon-to-deprecate `backstage-common` package. -- 1897169: More detailed deprecation messages -- Updated dependencies - - @backstage/backend-common@0.23.0 - - @backstage/backend-plugin-api@0.6.19 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.24-next.3 - -### Patch Changes - -- 1897169: More detailed deprecation messages -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.3 - - @backstage/backend-common@0.23.0-next.3 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.24-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.2 - - @backstage/backend-common@0.23.0-next.2 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.24-next.1 - -### Patch Changes - -- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` -- Updated dependencies - - @backstage/backend-plugin-api@0.6.19-next.1 - - @backstage/backend-common@0.23.0-next.1 - -## 0.5.24-next.0 - -### Patch Changes - -- 736bc3c: Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api` and `@backstage/backend-defaults` -- 6a576dc: Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependency on the soon-to-deprecate `backstage-common` package. -- Updated dependencies - - @backstage/backend-common@0.22.1-next.0 - - @backstage/backend-plugin-api@0.6.19-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.23 - -### Patch Changes - -- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. -- Updated dependencies - - @backstage/backend-common@0.22.0 - - @backstage/backend-plugin-api@0.6.18 - -## 0.5.23-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.22.0-next.1 - - @backstage/backend-plugin-api@0.6.18-next.1 - -## 0.5.23-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.8-next.0 - - @backstage/backend-plugin-api@0.6.18-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.22 - -### Patch Changes - -- d5a1fe1: Replaced winston logger with `LoggerService` -- Updated dependencies - - @backstage/backend-common@0.21.7 - - @backstage/backend-plugin-api@0.6.17 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.22-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.7-next.1 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.22-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.7-next.0 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.21 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.6 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.20 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.5 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.19 - -### Patch Changes - -- 0fb419b: Updated dependency `uuid` to `^9.0.0`. - Updated dependency `@types/uuid` to `^9.0.0`. -- Updated dependencies - - @backstage/backend-common@0.21.4 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.5.19-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.4-next.2 - - @backstage/config@1.2.0-next.1 - - @backstage/errors@1.2.4-next.0 - - @backstage/types@1.1.1 - -## 0.5.19-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.2.0-next.1 - - @backstage/backend-common@0.21.4-next.1 - - @backstage/errors@1.2.4-next.0 - - @backstage/types@1.1.1 - -## 0.5.18-next.0 - -### Patch Changes - -- 0fb419b: Updated dependency `uuid` to `^9.0.0`. - Updated dependency `@types/uuid` to `^9.0.0`. -- Updated dependencies - - @backstage/backend-common@0.21.3-next.0 - - @backstage/errors@1.2.4-next.0 - - @backstage/config@1.1.2-next.0 - - @backstage/types@1.1.1 - -## 0.5.15 - -### Patch Changes - -- 9aac2b0: Use `--cwd` as the first `yarn` argument -- 6707216: The `TaskScheduler.fromConfig` method now accepts the `LegacyRootDatabaseService` interface rather than the full `DatabaseManager` implementation. -- b68248b: Updated dependency `cron` to `^3.0.0`. -- Updated dependencies - - @backstage/backend-common@0.21.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.15-next.3 - -### Patch Changes - -- 6707216: The `TaskScheduler.fromConfig` method now accepts the `LegacyRootDatabaseService` interface rather than the full `DatabaseManager` implementation. -- b68248b: Updated dependency `cron` to `^3.0.0`. -- Updated dependencies - - @backstage/backend-common@0.21.0-next.3 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.15-next.2 - -### Patch Changes - -- 9aac2b0: Use `--cwd` as the first `yarn` argument -- Updated dependencies - - @backstage/backend-common@0.21.0-next.2 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.15-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.0-next.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.15-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.21.0-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.14 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.14-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.2 - -## 0.5.14-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.14-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.13 - -### Patch Changes - -- d8f488a: Allow tasks to run more often that the default work check interval, which is 5 seconds. -- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 -- Updated dependencies - - @backstage/backend-common@0.20.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.13-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0-next.3 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.13-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0-next.2 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.13-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0-next.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.13-next.0 - -### Patch Changes - -- d8f488aaa8: Allow tasks to run more often that the default work check interval, which is 5 seconds. -- Updated dependencies - - @backstage/backend-common@0.20.0-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.12 - -### Patch Changes - -- 013611b42e: `knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support. -- Updated dependencies - - @backstage/backend-common@0.19.9 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.12-next.2 - -### Patch Changes - -- [#20570](https://github.com/backstage/backstage/pull/20570) [`013611b42e`](https://github.com/backstage/backstage/commit/013611b42ed457fefa9bb85fddf416cf5e0c1f76) Thanks [@freben](https://github.com/freben)! - `knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support. - -- Updated dependencies - - @backstage/backend-common@0.19.9-next.2 - -## 0.5.12-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.9-next.1 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.12-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.9-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.11 - -### Patch Changes - -- 5db102bfdf: Instrument `backend-tasks` with some counters and histograms for duration. - - `backend_tasks.task.runs.count`: Counter with the total number of times a task has been run. - `backend_tasks.task.runs.duration`: Histogram with the run durations for each task. - - Both these metrics have come with `result` `taskId` and `scope` labels for finer grained grouping. - -- ddd76ac98d: Fix bug where backend tasks that are defined with HumanDuration are immediately triggered on application startup -- Updated dependencies - - @backstage/backend-common@0.19.8 - - @backstage/errors@1.2.3 - - @backstage/config@1.1.1 - - @backstage/types@1.1.1 - -## 0.5.11-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.8-next.2 - - @backstage/errors@1.2.3-next.0 - - @backstage/config@1.1.1-next.0 - - @backstage/types@1.1.1 - -## 0.5.10-next.1 - -### Patch Changes - -- 5db102bfdf: Instrument `backend-tasks` with some counters and histograms for duration. - - `backend_tasks.task.runs.count`: Counter with the total number of times a task has been run. - `backend_tasks.task.runs.duration`: Histogram with the run durations for each task. - - Both these metrics have come with `result` `taskId` and `scope` labels for finer grained grouping. - -- ddd76ac98d: Fix bug where backend tasks that are defined with HumanDuration are immediately triggered on application startup -- Updated dependencies - - @backstage/backend-common@0.19.7-next.1 - - @backstage/config@1.1.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - -## 0.5.10-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.7-next.0 - - @backstage/config@1.1.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - -## 0.5.8 - -### Patch Changes - -- 8fd91547cd0b: When starting a task that existed before, with a faster schedule than it - previously had, the task will now correctly obey the faster schedule - immediately. Before this fix, the new schedule was only obeyed after the next - pending (according to the old schedule) run had completed. -- 62f448edb0b5: Use `readDurationFromConfig` from the config package -- 74604806aae8: Avoid starting task janitor in tests. -- cfc3ca6ce060: Changes needed to support MySQL -- 814feeed7343: Update to handle invalid luxon values -- Updated dependencies - - @backstage/backend-common@0.19.5 - - @backstage/config@1.1.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - -## 0.5.8-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.1.0-next.2 - - @backstage/errors@1.2.2-next.0 - - @backstage/types@1.1.1-next.0 - - @backstage/backend-common@0.19.5-next.3 - -## 0.5.8-next.2 - -### Patch Changes - -- 814feeed7343: Update to handle invalid luxon values -- Updated dependencies - - @backstage/config@1.1.0-next.1 - - @backstage/backend-common@0.19.5-next.2 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.8-next.1 - -### Patch Changes - -- 62f448edb0b5: Use `readDurationFromConfig` from the config package -- Updated dependencies - - @backstage/config@1.1.0-next.0 - - @backstage/backend-common@0.19.5-next.1 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.7-next.0 - -### Patch Changes - -- cfc3ca6ce060: Changes needed to support MySQL -- Updated dependencies - - @backstage/backend-common@0.19.4-next.0 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.5 - -### Patch Changes - -- dfd1b6b2fc33: Make `readTaskScheduleDefinitionFromConfig` properly handle bad inputs -- Updated dependencies - - @backstage/backend-common@0.19.2 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.5-next.2 - -### Patch Changes - -- dfd1b6b2fc33: Make `readTaskScheduleDefinitionFromConfig` properly handle bad inputs -- Updated dependencies - - @backstage/backend-common@0.19.2-next.2 - -## 0.5.5-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.2-next.1 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.5-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.2-next.0 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.4 - -### Patch Changes - -- Updated dependencies - - @backstage/errors@1.2.1 - - @backstage/backend-common@0.19.1 - - @backstage/config@1.0.8 - - @backstage/types@1.1.0 - -## 0.5.4-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/errors@1.2.1-next.0 - - @backstage/backend-common@0.19.1-next.0 - - @backstage/config@1.0.8 - - @backstage/types@1.1.0 - -## 0.5.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.0 - - @backstage/types@1.1.0 - - @backstage/errors@1.2.0 - - @backstage/config@1.0.8 - -## 0.5.3-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.0-next.2 - - @backstage/config@1.0.7 - - @backstage/errors@1.2.0-next.0 - - @backstage/types@1.0.2 - -## 0.5.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.0-next.1 - - @backstage/errors@1.2.0-next.0 - - @backstage/config@1.0.7 - - @backstage/types@1.0.2 - -## 0.5.3-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.6-next.0 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.5 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.5-next.1 - - @backstage/config@1.0.7 - -## 0.5.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.5-next.0 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.1 - -### Patch Changes - -- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. -- Updated dependencies - - @backstage/backend-common@0.18.4 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.1-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.4-next.2 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.1-next.1 - -### Patch Changes - -- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. -- Updated dependencies - - @backstage/backend-common@0.18.4-next.1 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.4-next.0 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.5.0 - -### Minor Changes - -- 1578276708a: add functionality to get descriptions from the scheduler for triggering - -### Patch Changes - -- f0685193efa: Added the adapted query to mysql and sqlite3 databases to not returning warning on logs -- 482dae5de1c: Updated link to docs. -- Updated dependencies - - @backstage/backend-common@0.18.3 - - @backstage/errors@1.1.5 - - @backstage/config@1.0.7 - - @backstage/types@1.0.2 - -## 0.5.0-next.2 - -### Minor Changes - -- 1578276708a: add functionality to get descriptions from the scheduler for triggering - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.3-next.2 - - @backstage/config@1.0.7-next.0 - -## 0.4.4-next.1 - -### Patch Changes - -- 482dae5de1c: Updated link to docs. -- Updated dependencies - - @backstage/errors@1.1.5-next.0 - - @backstage/backend-common@0.18.3-next.1 - - @backstage/config@1.0.7-next.0 - - @backstage/types@1.0.2 - -## 0.4.4-next.0 - -### Patch Changes - -- f0685193ef: Added the adapted query to mysql and sqlite3 databases to not returning warning on logs -- Updated dependencies - - @backstage/backend-common@0.18.3-next.0 - - @backstage/config@1.0.6 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.2 - - @backstage/config@1.0.6 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.3-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.2-next.2 - - @backstage/config@1.0.6 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.2-next.1 - - @backstage/config@1.0.6 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.3-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.2-next.0 - -## 0.4.1 - -### Patch Changes - -- 3fad4ed40a: Added a new static `TaskScheduler.forPlugin` method. -- b99c030f1b: Minor internal refactor to avoid import cycle issue. -- Updated dependencies - - @backstage/backend-common@0.18.0 - - @backstage/config@1.0.6 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.1-next.1 - -### Patch Changes - -- b99c030f1b: Minor internal refactor to avoid import cycle issue. -- Updated dependencies - - @backstage/backend-common@0.18.0-next.1 - - @backstage/config@1.0.6-next.0 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.0-next.0 - - @backstage/config@1.0.6-next.0 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.4.0 - -### Minor Changes - -- de8a975911: Changed to use native `AbortController` and `AbortSignal` from Node.js, instead - of the one from `node-abort-controller`. This is possible now that the minimum - supported Node.js version of the project is 16. - - Note that their interfaces are very slightly different, but typically not in a - way that matters to consumers. If you see any typescript errors as a direct - result from this, they are compatible with each other in the ways that we - interact with them, and should be possible to type-cast across without ill - effects. - -### Patch Changes - -- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries -- Updated dependencies - - @backstage/backend-common@0.17.0 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - - @backstage/config@1.0.5 - -## 0.4.0-next.3 - -### Patch Changes - -- b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries -- Updated dependencies - - @backstage/backend-common@0.17.0-next.3 - - @backstage/config@1.0.5-next.1 - - @backstage/errors@1.1.4-next.1 - - @backstage/types@1.0.2-next.1 - -## 0.4.0-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.17.0-next.2 - - @backstage/config@1.0.5-next.1 - - @backstage/errors@1.1.4-next.1 - - @backstage/types@1.0.2-next.1 - -## 0.4.0-next.1 - -### Minor Changes - -- de8a975911: Changed to use native `AbortController` and `AbortSignal` from Node.js, instead - of the one from `node-abort-controller`. This is possible now that the minimum - supported Node.js version of the project is 16. - - Note that their interfaces are very slightly different, but typically not in a - way that matters to consumers. If you see any typescript errors as a direct - result from this, they are compatible with each other in the ways that we - interact with them, and should be possible to type-cast across without ill - effects. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.17.0-next.1 - - @backstage/types@1.0.2-next.1 - - @backstage/config@1.0.5-next.1 - - @backstage/errors@1.1.4-next.1 - -## 0.3.8-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.16.1-next.0 - - @backstage/types@1.0.2-next.0 - - @backstage/config@1.0.5-next.0 - - @backstage/errors@1.1.4-next.0 - -## 0.3.7 - -### Patch Changes - -- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. -- Updated dependencies - - @backstage/backend-common@0.16.0 - - @backstage/types@1.0.1 - - @backstage/config@1.0.4 - - @backstage/errors@1.1.3 - -## 0.3.7-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.16.0-next.1 - - @backstage/config@1.0.4-next.0 - - @backstage/errors@1.1.3-next.0 - - @backstage/types@1.0.1-next.0 - -## 0.3.7-next.0 - -### Patch Changes - -- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. -- Updated dependencies - - @backstage/backend-common@0.16.0-next.0 - - @backstage/types@1.0.1-next.0 - - @backstage/config@1.0.4-next.0 - - @backstage/errors@1.1.3-next.0 - -## 0.3.6 - -### Patch Changes - -- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. -- Updated dependencies - - @backstage/backend-common@0.15.2 - - @backstage/config@1.0.3 - - @backstage/errors@1.1.2 - - @backstage/types@1.0.0 - -## 0.3.6-next.2 - -### Patch Changes - -- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. -- Updated dependencies - - @backstage/backend-common@0.15.2-next.2 - - @backstage/config@1.0.3-next.2 - - @backstage/errors@1.1.2-next.2 - - @backstage/types@1.0.0 - -## 0.3.6-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.15.2-next.1 - - @backstage/config@1.0.3-next.1 - - @backstage/errors@1.1.2-next.1 - - @backstage/types@1.0.0 - -## 0.3.6-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.15.2-next.0 - - @backstage/config@1.0.3-next.0 - - @backstage/errors@1.1.2-next.0 - - @backstage/types@1.0.0 - -## 0.3.5 - -### Patch Changes - -- 243533ecdc: Added support to mysql on some raw queries -- 8872cc735d: Fixed a bug where the database option to skip migrations was ignored. -- Updated dependencies - - @backstage/backend-common@0.15.1 - - @backstage/config@1.0.2 - - @backstage/errors@1.1.1 - -## 0.3.5-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.0.2-next.0 - - @backstage/errors@1.1.1-next.0 - - @backstage/backend-common@0.15.1-next.3 - -## 0.3.5-next.0 - -### Patch Changes - -- 243533ecdc: Added support to mysql on some raw queries -- 8872cc735d: Fixed a bug where the database option to skip migrations was ignored. -- Updated dependencies - - @backstage/backend-common@0.15.1-next.0 - -## 0.3.4 - -### Patch Changes - -- 29f782eb37: Updated dependency `@types/luxon` to `^3.0.0`. -- Updated dependencies - - @backstage/backend-common@0.15.0 - -## 0.3.4-next.0 - -### Patch Changes - -- 29f782eb37: Updated dependency `@types/luxon` to `^3.0.0`. -- Updated dependencies - - @backstage/backend-common@0.15.0-next.0 - -## 0.3.3 - -### Patch Changes - -- 4e9a90e307: Updated dependency `luxon` to `^3.0.0`. -- 679b32172e: Updated dependency `knex` to `^2.0.0`. -- Updated dependencies - - @backstage/backend-common@0.14.1 - - @backstage/errors@1.1.0 - -## 0.3.3-next.3 - -### Patch Changes - -- 4e9a90e307: Updated dependency `luxon` to `^3.0.0`. -- Updated dependencies - - @backstage/backend-common@0.14.1-next.3 - -## 0.3.3-next.2 - -### Patch Changes - -- 679b32172e: Updated dependency `knex` to `^2.0.0`. -- Updated dependencies - - @backstage/backend-common@0.14.1-next.2 - -## 0.3.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.14.1-next.1 - - @backstage/errors@1.1.0-next.0 - -## 0.3.3-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.14.1-next.0 - -## 0.3.2 - -### Patch Changes - -- fde10d24f6: Allow tasks that fail to retry on a loop emitting a warning log every time it fails with the amount of attempts it has -- f7146b516f: Updated dependency `cron` to `^2.0.0`. - Updated dependency `@types/cron` to `^2.0.0`. -- 7f108513b8: Add error logging when a background task throws an error rather than silently swallowing it. -- Updated dependencies - - @backstage/backend-common@0.14.0 - -## 0.3.2-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.14.0-next.2 - -## 0.3.2-next.1 - -### Patch Changes - -- f7146b516f: Updated dependency `cron` to `^2.0.0`. - Updated dependency `@types/cron` to `^2.0.0`. -- 7f108513b8: Add error logging when a background task throws an error rather than silently swallowing it. -- Updated dependencies - - @backstage/backend-common@0.13.6-next.1 - -## 0.3.2-next.0 - -### Patch Changes - -- fde10d24f6: Allow tasks that fail to retry on a loop emitting a warning log every time it fails with the amount of attempts it has -- Updated dependencies - - @backstage/backend-common@0.13.6-next.0 - -## 0.3.1 - -### Patch Changes - -- 73480846dd: `TaskScheduleDefinition` has been updated to also accept an options object containing duration information in the form of days, hours, seconds and so on. This allows for scheduling without importing `luxon`. - - ```diff - -import { Duration } from 'luxon'; - // omitted other code - - const schedule = env.scheduler.createScheduledTaskRunner({ - - frequency: Duration.fromObject({ minutes: 10 }), - - timeout: Duration.fromObject({ minutes: 15 }), - + frequency: { minutes: 10 }, - + timeout: { minutes: 15 }, - // omitted other code - }); - ``` - -- cfd779a9bc: Scheduled tasks now have an optional `scope` field. If unset, or having the - value `'global'`, the old behavior with cross-worker locking is retained. If - having the value `'local'`, there is no coordination across workers and the - behavior is more like `setInterval`. This can be used to replace usages of - `runPeriodically` helpers. -- ebbec677e1: Correctly set next run time for tasks -- Updated dependencies - - @backstage/backend-common@0.13.3 - - @backstage/config@1.0.1 - -## 0.3.1-next.1 - -### Patch Changes - -- 73480846dd: `TaskScheduleDefinition` has been updated to also accept an options object containing duration information in the form of days, hours, seconds and so on. This allows for scheduling without importing `luxon`. - - ```diff - -import { Duration } from 'luxon'; - // omitted other code - - const schedule = env.scheduler.createScheduledTaskRunner({ - - frequency: Duration.fromObject({ minutes: 10 }), - - timeout: Duration.fromObject({ minutes: 15 }), - + frequency: { minutes: 10 }, - + timeout: { minutes: 15 }, - // omitted other code - }); - ``` - -- ebbec677e1: Correctly set next run time for tasks -- Updated dependencies - - @backstage/backend-common@0.13.3-next.2 - - @backstage/config@1.0.1-next.0 - -## 0.3.1-next.0 - -### Patch Changes - -- cfd779a9bc: Scheduled tasks now have an optional `scope` field. If unset, or having the - value `'global'`, the old behavior with cross-worker locking is retained. If - having the value `'local'`, there is no coordination across workers and the - behavior is more like `setInterval`. This can be used to replace usages of - `runPeriodically` helpers. -- Updated dependencies - - @backstage/backend-common@0.13.3-next.0 - -## 0.3.0 - -### Minor Changes - -- ab008a0988: Adds the ability to manually trigger tasks which are registered - -### Patch Changes - -- bdd2773202: Refactored the internal `TaskWorker` class to make it easier to test. -- a83babdd63: Fixed the `initialDelay` parameter of tasks to properly make task workers - _always_ wait before the first invocations on startup, not just the very first - time that the task is ever created. This behavior is more in line with - expectations. Callers to not need to update their code. - - Also clarified in the doc comment for the field that this wait applies only on - an individual worker level. That is, if you have a cluster of workers then each - individual machine may postpone its first task invocation by the given amount of - time to leave room for the service to settle, but _other_ workers may still - continue to invoke the task on the regular cadence in the meantime. - -- Updated dependencies - - @backstage/backend-common@0.13.2 - -## 0.3.0-next.2 - -### Patch Changes - -- a83babdd63: Fixed the `initialDelay` parameter of tasks to properly make task workers - _always_ wait before the first invocations on startup, not just the very first - time that the task is ever created. This behavior is more in line with - expectations. Callers to not need to update their code. - - Also clarified in the doc comment for the field that this wait applies only on - an individual worker level. That is, if you have a cluster of workers then each - individual machine may postpone its first task invocation by the given amount of - time to leave room for the service to settle, but _other_ workers may still - continue to invoke the task on the regular cadence in the meantime. - -## 0.3.0-next.1 - -### Minor Changes - -- ab008a0988: Adds the ability to manually trigger tasks which are registered - -### Patch Changes - -- bdd2773202: Refactored the internal `TaskWorker` class to make it easier to test. -- Updated dependencies - - @backstage/backend-common@0.13.2-next.1 - -## 0.2.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.2-next.0 - -## 0.2.1 - -### Patch Changes - -- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` -- Updated dependencies - - @backstage/backend-common@0.13.1 - - @backstage/config@1.0.0 - - @backstage/errors@1.0.0 - - @backstage/types@1.0.0 - -## 0.2.0 - -### Minor Changes - -- 9461f73643: **BREAKING**: The `TaskDefinition` type has been removed, and replaced by the equal pair `TaskScheduleDefinition` and `TaskInvocationDefinition`. The interface for `PluginTaskScheduler.scheduleTask` stays effectively unchanged, so this only affects you if you use the actual types directly. - - Added the method `PluginTaskScheduler.createTaskSchedule`, which returns a `TaskSchedule` wrapper that is convenient to pass down into classes that want to control their task invocations while the caller wants to retain control of the actual schedule chosen. - -### Patch Changes - -- ab7cd7d70e: Do some groundwork for supporting the `better-sqlite3` driver, to maybe eventually replace `@vscode/sqlite3` (#9912) -- 7290dda9d4: Relaxed the task ID requirement to now support any non-empty string -- ae2ed04076: Add support for cron syntax to configure task frequency - `TaskScheduleDefinition.frequency` can now be both a `Duration` and an object on the form `{ cron: string }`, where the latter is expected to be on standard crontab format (e.g. `'0 */2 * * *'`). -- Updated dependencies - - @backstage/backend-common@0.13.0 - -## 0.2.0-next.0 - -### Minor Changes - -- 9461f73643: **BREAKING**: The `TaskDefinition` type has been removed, and replaced by the equal pair `TaskScheduleDefinition` and `TaskInvocationDefinition`. The interface for `PluginTaskScheduler.scheduleTask` stays effectively unchanged, so this only affects you if you use the actual types directly. - - Added the method `PluginTaskScheduler.createTaskSchedule`, which returns a `TaskSchedule` wrapper that is convenient to pass down into classes that want to control their task invocations while the caller wants to retain control of the actual schedule chosen. - -### Patch Changes - -- ab7cd7d70e: Do some groundwork for supporting the `better-sqlite3` driver, to maybe eventually replace `@vscode/sqlite3` (#9912) -- 7290dda9d4: Relaxed the task ID requirement to now support any non-empty string -- Updated dependencies - - @backstage/backend-common@0.13.0-next.0 - -## 0.1.10 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.12.0 - -## 0.1.9 - -### Patch Changes - -- dc97845422: Only output janitor logs when actually timing out tasks -- Updated dependencies - - @backstage/backend-common@0.11.0 - -## 0.1.8 - -### Patch Changes - -- Fix for the previous release with missing type declarations. -- Updated dependencies - - @backstage/backend-common@0.10.9 - - @backstage/config@0.1.15 - - @backstage/errors@0.2.2 - - @backstage/types@0.1.3 - -## 0.1.7 - -### Patch Changes - -- c77c5c7eb6: Added `backstage.role` to `package.json` -- Updated dependencies - - @backstage/backend-common@0.10.8 - - @backstage/errors@0.2.1 - - @backstage/config@0.1.14 - - @backstage/types@0.1.2 - -## 0.1.6 - -### Patch Changes - -- 2441d1cf59: chore(deps): bump `knex` from 0.95.6 to 1.0.2 - - This also replaces `sqlite3` with `@vscode/sqlite3` 5.0.7 - -- Updated dependencies - - @backstage/backend-common@0.10.7 - -## 0.1.6-next.0 - -### Patch Changes - -- 2441d1cf59: chore(deps): bump `knex` from 0.95.6 to 1.0.2 - - This also replaces `sqlite3` with `@vscode/sqlite3` 5.0.7 - -- Updated dependencies - - @backstage/backend-common@0.10.7-next.0 - -## 0.1.5 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.6 - -## 0.1.5-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.6-next.0 - -## 0.1.4 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.4 - - @backstage/config@0.1.13 - -## 0.1.4-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.4-next.0 - - @backstage/config@0.1.13-next.0 - -## 0.1.3 - -### Patch Changes - -- Updated dependencies - - @backstage/config@0.1.12 - - @backstage/backend-common@0.10.3 - - @backstage/errors@0.2.0 - -## 0.1.2 - -### Patch Changes - -- e188b37024: Updated README to clarify the following: - - - Dashes cannot be used in the `id`, changed to an underscore in the example - - The `timeout` is required, this was also added to the example - - Added a note about tasks not running as expected for local development using persistent database - -- Updated dependencies - - @backstage/backend-common@0.10.2 - -## 0.1.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.0 diff --git a/packages/backend-tasks/README.md b/packages/backend-tasks/README.md deleted file mode 100644 index 57cd63b0e0..0000000000 --- a/packages/backend-tasks/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# @backstage/backend-tasks - -Common distributed task management for Backstage backends. - -## Usage - -Add the library to your backend package: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/backend-tasks -``` - -then make use of its facilities as necessary: - -```typescript -import { TaskScheduler } from '@backstage/backend-tasks'; - -const scheduler = TaskScheduler.fromConfig(rootConfig).forPlugin('my-plugin'); - -await scheduler.scheduleTask({ - id: 'refresh_things', - frequency: { cron: '*/5 * * * *' }, // every 5 minutes, also supports Duration - timeout: { minutes: 15 }, - fn: async () => { - await entityProvider.run(); - }, -}); -``` - -## 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 deleted file mode 100644 index c0f9da3a01..0000000000 --- a/packages/backend-tasks/api-report.md +++ /dev/null @@ -1,114 +0,0 @@ -## API Report File for "@backstage/backend-tasks" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -import { Config } from '@backstage/config'; -import { Duration } from 'luxon'; -import { HumanDuration as HumanDuration_2 } from '@backstage/types'; -import { JsonObject } from '@backstage/types'; -import { LegacyRootDatabaseService } from '@backstage/backend-common'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginDatabaseManager } from '@backstage/backend-common'; - -// @public @deprecated -export type HumanDuration = HumanDuration_2; - -// @public @deprecated -export interface PluginTaskScheduler { - createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; - getScheduledTasks(): Promise; - scheduleTask( - task: TaskScheduleDefinition & TaskInvocationDefinition, - ): Promise; - triggerTask(id: string): Promise; -} - -// @public @deprecated -export function readTaskScheduleDefinitionFromConfig( - config: Config, -): TaskScheduleDefinition; - -// @public @deprecated -export type TaskDescriptor = { - id: string; - scope: 'global' | 'local'; - settings: { - version: number; - } & JsonObject; -}; - -// @public @deprecated -export type TaskFunction = - | ((abortSignal: AbortSignal) => void | Promise) - | (() => void | Promise); - -// @public @deprecated -export interface TaskInvocationDefinition { - fn: TaskFunction; - id: string; - signal?: AbortSignal; -} - -// @public @deprecated -export interface TaskRunner { - run(task: TaskInvocationDefinition): Promise; -} - -// @public @deprecated -export interface TaskScheduleDefinition { - frequency: - | { - cron: string; - } - | Duration - | 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; -} - -// @public @deprecated -export interface TaskScheduleDefinitionConfig { - frequency: - | { - cron: string; - } - | string - | HumanDuration_2; - initialDelay?: string | HumanDuration_2; - scope?: 'global' | 'local'; - timeout: string | HumanDuration_2; -} - -// @public @deprecated -export class TaskScheduler { - constructor( - databaseManager: LegacyRootDatabaseService, - logger: LoggerService, - ); - // @deprecated - forPlugin(pluginId: string): PluginTaskScheduler; - // @deprecated (undocumented) - static forPlugin(opts: { - pluginId: string; - databaseManager: PluginDatabaseManager; - logger: LoggerService; - }): PluginTaskScheduler; - // @deprecated (undocumented) - static fromConfig( - config: Config, - options?: { - databaseManager?: LegacyRootDatabaseService; - logger?: LoggerService; - }, - ): TaskScheduler; -} -``` diff --git a/packages/backend-tasks/catalog-info.yaml b/packages/backend-tasks/catalog-info.yaml deleted file mode 100644 index f9b81066e6..0000000000 --- a/packages/backend-tasks/catalog-info.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: backstage-backend-tasks - title: '@backstage/backend-tasks' - description: Common distributed task management library for Backstage backends -spec: - lifecycle: experimental - type: backstage-node-library - owner: maintainers diff --git a/packages/backend-tasks/knip-report.md b/packages/backend-tasks/knip-report.md deleted file mode 100644 index 2661c35327..0000000000 --- a/packages/backend-tasks/knip-report.md +++ /dev/null @@ -1,2 +0,0 @@ -# Knip report - diff --git a/packages/backend-tasks/migrations/20210928160613_init.js b/packages/backend-tasks/migrations/20210928160613_init.js deleted file mode 100644 index f9900cab11..0000000000 --- a/packages/backend-tasks/migrations/20210928160613_init.js +++ /dev/null @@ -1,64 +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. - */ - -// @ts-check - -/** - * @param {import('knex').Knex} knex - */ -exports.up = async function up(knex) { - // - // tasks - // - await knex.schema.createTable('backstage_backend_tasks__tasks', table => { - table.comment('Tasks used for scheduling work on multiple workers'); - table - .string('id') - .primary() - .notNullable() - .comment('The unique ID of this particular task'); - table - .text('settings_json') - .notNullable() - .comment('JSON serialized object with properties for this task'); - table - .dateTime('next_run_start_at') - .notNullable() - .comment('The next time that the task should be started'); - table - .text('current_run_ticket') - .nullable() - .comment('A unique ticket for the current task run'); - table - .dateTime('current_run_started_at') - .nullable() - .comment('The time that the current task run started'); - table - .dateTime('current_run_expires_at') - .nullable() - .comment('The time that the current task run will time out'); - }); -}; - -/** - * @param {import('knex').Knex} knex - */ -exports.down = async function down(knex) { - // - // tasks - // - await knex.schema.dropTable('backstage_backend_tasks__tasks'); -}; diff --git a/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js b/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js deleted file mode 100644 index efbef1c76f..0000000000 --- a/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 deleted file mode 100644 index 3f482b38e6..0000000000 --- a/packages/backend-tasks/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@backstage/backend-tasks", - "description": "Common distributed task management library for Backstage backends", - "version": "0.5.28-next.2", - "main": "src/index.ts", - "types": "src/index.ts", - "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" - }, - "backstage": { - "role": "node-library" - }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-tasks" - }, - "keywords": [ - "backstage" - ], - "license": "Apache-2.0", - "scripts": { - "build": "backstage-cli package build", - "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" - }, - "dependencies": { - "@backstage/backend-common": "workspace:^", - "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^", - "@backstage/types": "workspace:^", - "@opentelemetry/api": "^1.3.0", - "@types/luxon": "^3.0.0", - "cron": "^3.0.0", - "knex": "^3.0.0", - "lodash": "^4.17.21", - "luxon": "^3.0.0", - "uuid": "^9.0.0", - "zod": "^3.22.4" - }, - "devDependencies": { - "@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/database/migrateBackendTasks.ts b/packages/backend-tasks/src/database/migrateBackendTasks.ts deleted file mode 100644 index ae5048fbf7..0000000000 --- a/packages/backend-tasks/src/database/migrateBackendTasks.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { resolvePackagePath } from '@backstage/backend-plugin-api'; -import { Knex } from 'knex'; -import { DB_MIGRATIONS_TABLE } from './tables'; - -export async function migrateBackendTasks(knex: Knex): Promise { - const migrationsDir = resolvePackagePath( - '@backstage/backend-tasks', - 'migrations', - ); - - await knex.migrate.latest({ - directory: migrationsDir, - tableName: DB_MIGRATIONS_TABLE, - }); -} diff --git a/packages/backend-tasks/src/database/tables.ts b/packages/backend-tasks/src/database/tables.ts deleted file mode 100644 index 63aad6e42a..0000000000 --- a/packages/backend-tasks/src/database/tables.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const DB_MIGRATIONS_TABLE = 'backstage_backend_tasks__knex_migrations'; -export const DB_TASKS_TABLE = 'backstage_backend_tasks__tasks'; - -export type DbTasksRow = { - id: string; - settings_json: string; - next_run_start_at: Date; - current_run_ticket?: string; - current_run_started_at?: Date | string; - current_run_expires_at?: Date | string; -}; diff --git a/packages/backend-tasks/src/deprecated.ts b/packages/backend-tasks/src/deprecated.ts deleted file mode 100644 index 629e5f6d4d..0000000000 --- a/packages/backend-tasks/src/deprecated.ts +++ /dev/null @@ -1,27 +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 { HumanDuration as TypesHumanDuration } from '@backstage/types'; - -/** - * Human friendly durations object. - * - * @public - * @deprecated Import from `@backstage/types` instead - */ -export type HumanDuration = TypesHumanDuration; - -export * from './tasks'; diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts deleted file mode 100644 index 0aae81b143..0000000000 --- a/packages/backend-tasks/src/index.ts +++ /dev/null @@ -1,23 +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. - */ - -/** - * Common distributed task management library for Backstage backends - * - * @packageDocumentation - */ - -export * from './deprecated'; diff --git a/packages/backend-tasks/src/migrations.test.ts b/packages/backend-tasks/src/migrations.test.ts deleted file mode 100644 index 7583948f71..0000000000 --- a/packages/backend-tasks/src/migrations.test.ts +++ /dev/null @@ -1,123 +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 { Knex } from 'knex'; -import { TestDatabases } from '@backstage/backend-test-utils'; -import fs from 'fs'; - -const migrationsDir = `${__dirname}/../migrations`; -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-tasks/src/setupTests.ts b/packages/backend-tasks/src/setupTests.ts deleted file mode 100644 index 76619a2542..0000000000 --- a/packages/backend-tasks/src/setupTests.ts +++ /dev/null @@ -1,25 +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 { TestDatabases } from '@backstage/backend-test-utils'; -import { Settings } from 'luxon'; - -// TS still thinks that methods can return null / placeholders, but we still want to throw as soon as possible when things go wrong -Settings.throwOnInvalid = true; - -TestDatabases.setDefaults({ - ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], -}); diff --git a/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts b/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts deleted file mode 100644 index d4d3d16067..0000000000 --- a/packages/backend-tasks/src/tasks/LocalTaskWorker.test.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 { LocalTaskWorker } from './LocalTaskWorker'; -import { mockServices } from '@backstage/backend-test-utils'; - -describe('LocalTaskWorker', () => { - const logger = mockServices.logger.mock(); - - it('runs the happy path (with iso duration) and handles cancellation', async () => { - const fn = jest.fn(); - const controller = new AbortController(); - - const worker = new LocalTaskWorker('a', fn, logger); - worker.start( - { - version: 2, - initialDelayDuration: 'PT0.2S', - cadence: 'PT0.2S', - timeoutAfterDuration: 'PT1S', - }, - { signal: controller.signal }, - ); - - // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 100)); - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 200)); - expect(fn).toHaveBeenCalledTimes(1); - await new Promise(r => setTimeout(r, 200)); - expect(fn).toHaveBeenCalledTimes(2); - controller.abort(); - await new Promise(r => setTimeout(r, 200)); - expect(fn).toHaveBeenCalledTimes(2); - }); - - it('runs the happy path (with a cron expression) and handles cancellation', async () => { - const fn = jest.fn(); - const controller = new AbortController(); - - // Await until system time is just past a second boundary (since cron is - // wall clock based) - await new Promise(r => setTimeout(r, 1000 - (Date.now() % 1000) + 10)); - - const worker = new LocalTaskWorker('a', fn, logger); - worker.start( - { - version: 2, - initialDelayDuration: 'PT0.2S', - cadence: '* * * * * *', - timeoutAfterDuration: 'PT1S', - }, - { signal: controller.signal }, - ); - - // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 100)); - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 200)); - expect(fn).toHaveBeenCalledTimes(1); - await new Promise(r => setTimeout(r, 1000)); - expect(fn).toHaveBeenCalledTimes(2); - controller.abort(); - await new Promise(r => setTimeout(r, 1000)); - expect(fn).toHaveBeenCalledTimes(2); - }); - - it('can trigger to abort wait', async () => { - const fn = jest.fn(); - const controller = new AbortController(); - - const worker = new LocalTaskWorker('a', fn, logger); - worker.start( - { - version: 2, - initialDelayDuration: 'PT0.2S', - cadence: 'PT0.2S', - timeoutAfterDuration: 'PT1S', - }, - { signal: controller.signal }, - ); - - // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 100)); - expect(fn).toHaveBeenCalledTimes(0); - await new Promise(r => setTimeout(r, 200)); - expect(fn).toHaveBeenCalledTimes(1); - worker.trigger(); - await new Promise(r => setTimeout(r, 10)); - expect(fn).toHaveBeenCalledTimes(2); - controller.abort(); - }); -}); diff --git a/packages/backend-tasks/src/tasks/LocalTaskWorker.ts b/packages/backend-tasks/src/tasks/LocalTaskWorker.ts deleted file mode 100644 index e79acb35f0..0000000000 --- a/packages/backend-tasks/src/tasks/LocalTaskWorker.ts +++ /dev/null @@ -1,154 +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 { ConflictError } from '@backstage/errors'; -import { CronTime } from 'cron'; -import { DateTime, Duration } from 'luxon'; -import { TaskFunction, TaskSettingsV2 } from './types'; -import { delegateAbortController, sleep } from './util'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -/** - * Implements tasks that run locally without cross-host collaboration. - * - * @private - */ -export class LocalTaskWorker { - private abortWait: AbortController | undefined; - - constructor( - private readonly taskId: string, - private readonly fn: TaskFunction, - private readonly logger: LoggerService, - ) {} - - start(settings: TaskSettingsV2, options?: { signal?: AbortSignal }) { - this.logger.info( - `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`, - ); - - (async () => { - let attemptNum = 1; - for (;;) { - try { - if (settings.initialDelayDuration) { - await this.sleep( - Duration.fromISO(settings.initialDelayDuration), - options?.signal, - ); - } - - while (!options?.signal?.aborted) { - const startTime = process.hrtime(); - await this.runOnce(settings, options?.signal); - const timeTaken = process.hrtime(startTime); - await this.waitUntilNext( - settings, - (timeTaken[0] + timeTaken[1] / 1e9) * 1000, - options?.signal, - ); - } - - this.logger.info(`Task worker finished: ${this.taskId}`); - attemptNum = 0; - break; - } catch (e) { - attemptNum += 1; - this.logger.warn( - `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`, - ); - await sleep(Duration.fromObject({ seconds: 1 })); - } - } - })(); - } - - trigger(): void { - if (!this.abortWait) { - throw new ConflictError(`Task ${this.taskId} is currently running`); - } - this.abortWait.abort(); - } - - /** - * Makes a single attempt at running the task to completion. - */ - private async runOnce( - settings: TaskSettingsV2, - signal?: AbortSignal, - ): Promise { - // Abort the task execution either if the worker is stopped, or if the - // task timeout is hit - const taskAbortController = delegateAbortController(signal); - const timeoutHandle = setTimeout(() => { - taskAbortController.abort(); - }, Duration.fromISO(settings.timeoutAfterDuration).as('milliseconds')); - - try { - await this.fn(taskAbortController.signal); - } catch (e) { - // ignore intentionally - } - - // release resources - clearTimeout(timeoutHandle); - taskAbortController.abort(); - } - - /** - * Sleeps until it's time to run the task again. - */ - private async waitUntilNext( - settings: TaskSettingsV2, - lastRunMillis: number, - signal?: AbortSignal, - ) { - if (signal?.aborted) { - return; - } - - const isCron = !settings.cadence.startsWith('P'); - let dt: number; - - if (isCron) { - const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate(); - dt = nextRun - Date.now(); - } else { - dt = - Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis; - } - - dt = Math.max(dt, 0); - - this.logger.debug( - `task: ${this.taskId} will next occur around ${DateTime.now().plus( - Duration.fromMillis(dt), - )}`, - ); - - await this.sleep(Duration.fromMillis(dt), signal); - } - - private async sleep( - duration: Duration, - abortSignal?: AbortSignal, - ): Promise { - this.abortWait = delegateAbortController(abortSignal); - await sleep(duration, this.abortWait.signal); - this.abortWait.abort(); // cleans up resources - this.abortWait = undefined; - } -} diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts deleted file mode 100644 index eca899098f..0000000000 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - TestDatabaseId, - TestDatabases, - mockServices, -} from '@backstage/backend-test-utils'; -import { ConflictError, NotFoundError } from '@backstage/errors'; -import { Duration } from 'luxon'; -import { migrateBackendTasks } from '../database/migrateBackendTasks'; -import { - parseDuration, - PluginTaskSchedulerImpl, -} from './PluginTaskSchedulerImpl'; - -function defer() { - let resolve = () => {}; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve }; -} - -jest.setTimeout(60_000); - -describe('PluginTaskManagerImpl', () => { - const databases = TestDatabases.create({ - ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], - }); - - beforeAll(async () => { - // Make sure all databases are running before mocking timers, in case of testcontainers - await Promise.all( - databases.eachSupportedId().map(([id]) => databases.init(id)), - ); - - jest.useFakeTimers(); - }, 60_000); - - async function init(databaseId: TestDatabaseId) { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - const logger = mockServices.logger.mock(); - const manager = new PluginTaskSchedulerImpl(async () => knex, logger); - return { knex, manager }; - } - - // This is just to test the wrapper code; most of the actual tests are in - // TaskWorker.test.ts - describe('scheduleTask with global scope', () => { - it.each(databases.eachSupportedId())( - 'can run the v1 happy path, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromMillis(5000), - fn, - scope: 'global', - }); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, - ); - - it.each(databases.eachSupportedId())( - 'can run the v2 happy path, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task2', - timeout: Duration.fromMillis(5000), - frequency: { cron: '* * * * * *' }, - fn, - scope: 'global', - }); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, - ); - }); - - describe('triggerTask with global scope', () => { - it.each(databases.eachSupportedId())( - 'can manually trigger a task, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - initialDelay: Duration.fromObject({ years: 1 }), - fn, - scope: 'global', - }); - - await manager.triggerTask('task1'); - jest.advanceTimersByTime(5000); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, - ); - - it.each(databases.eachSupportedId())( - 'cant trigger a non-existent task, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const fn = jest.fn(); - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - fn, - scope: 'global', - }); - - await expect(() => manager.triggerTask('task2')).rejects.toThrow( - NotFoundError, - ); - }, - ); - - it.each(databases.eachSupportedId())( - 'cant trigger a running task, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const { promise, resolve } = defer(); - - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - fn: async () => { - resolve(); - await new Promise(r => setTimeout(r, 20000)); - }, - scope: 'global', - }); - - await promise; - await expect(() => manager.triggerTask('task1')).rejects.toThrow( - ConflictError, - ); - }, - ); - }); - - // This is just to test the wrapper code; most of the actual tests are in - // TaskWorker.test.ts - describe('scheduleTask with local scope', () => { - it('can run the v1 happy path', async () => { - const { manager } = await init('SQLITE_3'); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task1', - timeout: { milliseconds: 5000 }, - frequency: { milliseconds: 5000 }, - fn, - scope: 'local', - }); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, 60_000); - - it('can run the v2 happy path', async () => { - const { manager } = await init('SQLITE_3'); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task2', - timeout: Duration.fromMillis(5000), - frequency: { cron: '* * * * * *' }, - fn, - scope: 'local', - }); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, 60_000); - }); - - describe('triggerTask with local scope', () => { - it('can manually trigger a task', async () => { - const { manager } = await init('SQLITE_3'); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - initialDelay: Duration.fromObject({ years: 1 }), - fn, - scope: 'local', - }); - - await manager.triggerTask('task1'); - jest.advanceTimersByTime(5000); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, 60_000); - - it('cant trigger a non-existent task', async () => { - const { manager } = await init('SQLITE_3'); - - const fn = jest.fn(); - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - fn, - scope: 'local', - }); - - await expect(() => manager.triggerTask('task2')).rejects.toThrow( - NotFoundError, - ); - }, 60_000); - - it('cant trigger a running task', async () => { - const { manager } = await init('SQLITE_3'); - - const { promise, resolve } = defer(); - - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromObject({ years: 1 }), - fn: async () => { - resolve(); - await new Promise(r => setTimeout(r, 20000)); - }, - scope: 'local', - }); - - await promise; - await expect(() => manager.triggerTask('task1')).rejects.toThrow( - ConflictError, - ); - }, 60_000); - }); - - // This is just to test the wrapper code; most of the actual tests are in - // TaskWorker.test.ts - describe('createScheduledTaskRunner', () => { - it.each(databases.eachSupportedId())( - 'can run the happy path, %p', - async databaseId => { - const { manager } = await init(databaseId); - - const fn = jest.fn(); - const promise = new Promise(resolve => fn.mockImplementation(resolve)); - await manager - .createScheduledTaskRunner({ - timeout: Duration.fromMillis(5000), - frequency: Duration.fromMillis(5000), - scope: 'global', - }) - .run({ - id: 'task1', - fn, - }); - - await promise; - expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); - }, - ); - }); - - describe('can fetch task ids', () => { - it.each(databases.eachSupportedId())( - 'can fetch both global and local task ids, %p', - async databaseId => { - const { manager } = await init(databaseId); - const fn = jest.fn(); - - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromMillis(5000), - fn, - scope: 'global', - }); - - await manager.scheduleTask({ - id: 'task2', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromMillis(5000), - fn, - scope: 'local', - }); - - await expect(manager.getScheduledTasks()).resolves.toEqual([ - { - id: 'task1', - scope: 'global', - settings: expect.objectContaining({ cadence: 'PT5S' }), - }, - { - id: 'task2', - scope: 'local', - settings: expect.objectContaining({ cadence: 'PT5S' }), - }, - ]); - }, - ); - }); - - describe('parseDuration', () => { - it('should parse durations', () => { - 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 deleted file mode 100644 index 0ccc77a6c1..0000000000 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Knex } from 'knex'; -import { Duration } from 'luxon'; -import { LocalTaskWorker } from './LocalTaskWorker'; -import { TaskWorker } from './TaskWorker'; -import { - PluginTaskScheduler, - TaskDescriptor, - TaskFunction, - TaskInvocationDefinition, - TaskRunner, - TaskScheduleDefinition, - TaskSettingsV2, -} from './types'; -import { validateId } from './util'; -import { Counter, Histogram, metrics } from '@opentelemetry/api'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -/** - * Implements the actual task management. - */ -export class PluginTaskSchedulerImpl implements PluginTaskScheduler { - private readonly localTasksById = new Map(); - private readonly allScheduledTasks: TaskDescriptor[] = []; - - private readonly counter: Counter; - private readonly duration: Histogram; - - constructor( - private readonly databaseFactory: () => Promise, - private readonly logger: LoggerService, - ) { - const meter = metrics.getMeter('default'); - this.counter = meter.createCounter('backend_tasks.task.runs.count', { - description: 'Total number of times a task has been run', - }); - this.duration = meter.createHistogram('backend_tasks.task.runs.duration', { - description: 'Histogram of task run durations', - unit: 'seconds', - }); - } - - async triggerTask(id: string): Promise { - const localTask = this.localTasksById.get(id); - if (localTask) { - localTask.trigger(); - return; - } - - const knex = await this.databaseFactory(); - await TaskWorker.trigger(knex, id); - } - - async scheduleTask( - task: TaskScheduleDefinition & TaskInvocationDefinition, - ): Promise { - validateId(task.id); - const scope = task.scope ?? 'global'; - - const settings: TaskSettingsV2 = { - version: 2, - cadence: parseDuration(task.frequency), - initialDelayDuration: - task.initialDelay && parseDuration(task.initialDelay), - timeoutAfterDuration: parseDuration(task.timeout), - }; - - if (scope === 'global') { - const knex = await this.databaseFactory(); - const worker = new TaskWorker( - task.id, - this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), - knex, - this.logger.child({ task: task.id }), - ); - await worker.start(settings, { signal: task.signal }); - } else { - const worker = new LocalTaskWorker( - task.id, - this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), - this.logger.child({ task: task.id }), - ); - worker.start(settings, { signal: task.signal }); - this.localTasksById.set(task.id, worker); - } - - this.allScheduledTasks.push({ - id: task.id, - scope: scope, - settings: settings, - }); - } - - createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner { - return { - run: async task => { - await this.scheduleTask({ ...task, ...schedule }); - }, - }; - } - - async getScheduledTasks(): Promise { - return this.allScheduledTasks; - } - - private wrapInMetrics( - fn: TaskFunction, - opts: { labels: Record }, - ): TaskFunction { - return async abort => { - const labels = { - ...opts.labels, - }; - this.counter.add(1, { ...labels, result: 'started' }); - - const startTime = process.hrtime(); - - try { - await fn(abort); - labels.result = 'completed'; - } catch (ex) { - labels.result = 'failed'; - throw ex; - } finally { - const delta = process.hrtime(startTime); - const endTime = delta[0] + delta[1] / 1e9; - this.counter.add(1, labels); - this.duration.record(endTime, labels); - } - }; - } -} - -export function parseDuration( - frequency: TaskScheduleDefinition['frequency'], -): string { - if ('cron' in frequency) { - return frequency.cron; - } - if ('trigger' in frequency) { - return frequency.trigger; - } - - const parsed = Duration.isDuration(frequency) - ? frequency - : Duration.fromObject(frequency); - - if (!parsed.isValid) { - throw new Error( - `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`, - ); - } - - return parsed.toISO()!; -} diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts deleted file mode 100644 index 93b5d7d189..0000000000 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; -import { Knex } from 'knex'; -import { Duration } from 'luxon'; -import waitForExpect from 'wait-for-expect'; -import { migrateBackendTasks } from '../database/migrateBackendTasks'; -import { DbTasksRow, DB_TASKS_TABLE } from '../database/tables'; -import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; -import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; - -const insertTask = async (knex: Knex, task: DbTasksRow) => { - return knex(DB_TASKS_TABLE) - .insert(task) - .onConflict('id') - .merge(['settings_json']); -}; - -const getTask = async (knex: Knex): Promise => { - return (await knex(DB_TASKS_TABLE))[0]; -}; - -describe('PluginTaskSchedulerJanitor', () => { - const logger = mockServices.logger.mock(); - const databases = TestDatabases.create({ - ids: [ - /* 'MYSQL_8' not supported yet */ - 'POSTGRES_16', - 'POSTGRES_12', - 'SQLITE_3', - 'MYSQL_8', - ], - }); - const testScopedSignal = createTestScopedSignal(); - - jest.setTimeout(60_000); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it.each(databases.eachSupportedId())( - 'Should update date if current_run_expires_at expires, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const dateYesterday = new Date( - new Date().setDate(new Date().getDate() - 1), - ); - - await insertTask(knex, { - id: 'task1', - settings_json: '', - next_run_start_at: new Date('2023-03-01 00:00:00'), - current_run_ticket: 'ticket', - current_run_started_at: dateYesterday, - current_run_expires_at: dateYesterday, - }); - - const worker = new PluginTaskSchedulerJanitor({ - waitBetweenRuns: Duration.fromObject({ milliseconds: 20 }), - knex, - logger, - }); - - worker.start(testScopedSignal()); - - await waitForExpect(async () => { - await expect(getTask(knex)).resolves.toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: null, - current_run_started_at: null, - current_run_expires_at: null, - }), - ); - }); - }, - ); -}); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts deleted file mode 100644 index 7b6becab91..0000000000 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Knex } from 'knex'; -import { Duration } from 'luxon'; -import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; -import { sleep } from './util'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -/** - * Makes sure to auto-expire and clean up things that time out or for other - * reasons should not be left lingering. - */ -export class PluginTaskSchedulerJanitor { - private readonly knex: Knex; - private readonly waitBetweenRuns: Duration; - private readonly logger: LoggerService; - - constructor(options: { - knex: Knex; - waitBetweenRuns: Duration; - logger: LoggerService; - }) { - this.knex = options.knex; - this.waitBetweenRuns = options.waitBetweenRuns; - this.logger = options.logger; - } - - async start(abortSignal?: AbortSignal) { - while (!abortSignal?.aborted) { - try { - await this.runOnce(); - } catch (e) { - this.logger.warn(`Error while performing janitorial tasks, ${e}`); - } - - await sleep(this.waitBetweenRuns, abortSignal); - } - } - - private async runOnce() { - const dbNull = this.knex.raw('null'); - const configClient = this.knex.client.config.client; - - let tasks: Array<{ id: string }>; - if (configClient.includes('sqlite3') || configClient.includes('mysql')) { - tasks = await this.knex(DB_TASKS_TABLE) - .select('id') - .where('current_run_expires_at', '<', this.knex.fn.now()); - await this.knex(DB_TASKS_TABLE) - .whereIn( - 'id', - tasks.map(t => t.id), - ) - .update({ - current_run_ticket: dbNull, - current_run_started_at: dbNull, - current_run_expires_at: dbNull, - }); - } else { - tasks = await this.knex(DB_TASKS_TABLE) - .where('current_run_expires_at', '<', this.knex.fn.now()) - .update({ - current_run_ticket: dbNull, - current_run_started_at: dbNull, - current_run_expires_at: dbNull, - }) - .returning(['id']); - } - - // In rare cases, knex drivers may ignore "returning", and return the number - // of rows changed instead - if (typeof tasks === 'number') { - if (tasks > 0) { - this.logger.warn(`${tasks} tasks timed out and were lost`); - } - } else { - for (const { id } of tasks) { - this.logger.warn(`Task timed out and was lost: ${id}`); - } - } - } -} diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts deleted file mode 100644 index 24ce73712f..0000000000 --- a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatabaseManager } from '@backstage/backend-common'; -import { - TestDatabaseId, - TestDatabases, - mockServices, -} from '@backstage/backend-test-utils'; -import { Duration } from 'luxon'; -import waitForExpect from 'wait-for-expect'; -import { TaskScheduler } from './TaskScheduler'; -import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; - -jest.setTimeout(60_000); - -describe('TaskScheduler', () => { - const logger = mockServices.logger.mock(); - const databases = TestDatabases.create(); - const testScopedSignal = createTestScopedSignal(); - - async function createDatabase( - databaseId: TestDatabaseId, - ): Promise { - const knex = await databases.init(databaseId); - const databaseManager: Partial = { - forPlugin: () => ({ - getClient: async () => knex, - }), - }; - return databaseManager as DatabaseManager; - } - - it.each(databases.eachSupportedId())( - 'can return a working v1 plugin impl, %p', - async databaseId => { - const database = await createDatabase(databaseId); - const manager = new TaskScheduler(database, logger).forPlugin('test'); - const fn = jest.fn(); - - await manager.scheduleTask({ - id: 'task1', - timeout: Duration.fromMillis(5000), - frequency: Duration.fromMillis(5000), - signal: testScopedSignal(), - fn, - }); - - await waitForExpect(() => { - expect(fn).toHaveBeenCalled(); - }); - }, - ); - - it.each(databases.eachSupportedId())( - 'can return a working v2 plugin impl, %p', - async databaseId => { - const database = await createDatabase(databaseId); - const manager = new TaskScheduler(database, logger).forPlugin('test'); - const fn = jest.fn(); - - await manager.scheduleTask({ - id: 'task2', - timeout: Duration.fromMillis(5000), - frequency: { cron: '* * * * * *' }, - signal: testScopedSignal(), - fn, - }); - - await waitForExpect(() => { - expect(fn).toHaveBeenCalled(); - }); - }, - ); -}); diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts deleted file mode 100644 index ff6fd12017..0000000000 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - DatabaseManager, - getRootLogger, - LegacyRootDatabaseService, - PluginDatabaseManager, -} from '@backstage/backend-common'; -import { Config } from '@backstage/config'; -import { once } from 'lodash'; -import { Duration } from 'luxon'; -import { migrateBackendTasks } from '../database/migrateBackendTasks'; -import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; -import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; -import { PluginTaskScheduler } from './types'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -/** - * Deals with the scheduling of distributed tasks. - * - * @public - * @deprecated 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` - */ -export class TaskScheduler { - /** - * @deprecated - * It is only used by the legacy backend system, and should not be used in the new backend system. - */ - static fromConfig( - config: Config, - options?: { - databaseManager?: LegacyRootDatabaseService; - logger?: LoggerService; - }, - ): TaskScheduler { - const databaseManager = - options?.databaseManager ?? DatabaseManager.fromConfig(config); - const logger = (options?.logger || getRootLogger()).child({ - type: 'taskManager', - }); - return new TaskScheduler(databaseManager, logger); - } - - constructor( - private readonly databaseManager: LegacyRootDatabaseService, - private readonly logger: LoggerService, - ) {} - - /** - * Instantiates a task manager instance for the given plugin. - * - * @param pluginId - The unique ID of the plugin, for example "catalog" - * @returns A {@link PluginTaskScheduler} instance - * @deprecated 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` - */ - forPlugin(pluginId: string): PluginTaskScheduler { - return TaskScheduler.forPlugin({ - pluginId, - databaseManager: this.databaseManager.forPlugin(pluginId), - logger: this.logger, - }); - } - - /** - * @deprecated 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` - */ - static forPlugin(opts: { - pluginId: string; - databaseManager: PluginDatabaseManager; - logger: LoggerService; - }): PluginTaskScheduler { - const databaseFactory = once(async () => { - const knex = await opts.databaseManager.getClient(); - - if (!opts.databaseManager.migrations?.skip) { - await migrateBackendTasks(knex); - } - - if (process.env.NODE_ENV !== 'test') { - const janitor = new PluginTaskSchedulerJanitor({ - knex, - waitBetweenRuns: Duration.fromObject({ minutes: 1 }), - logger: opts.logger, - }); - janitor.start(); - } - - return knex; - }); - - return new PluginTaskSchedulerImpl(databaseFactory, opts.logger); - } -} diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts deleted file mode 100644 index 829d7754f8..0000000000 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; -import { Duration, DateTime } from 'luxon'; -import waitForExpect from 'wait-for-expect'; -import { migrateBackendTasks } from '../database/migrateBackendTasks'; -import { DbTasksRow, DB_TASKS_TABLE } from '../database/tables'; -import { TaskWorker } from './TaskWorker'; -import { TaskSettingsV2 } from './types'; -import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; - -jest.setTimeout(60_000); - -describe('TaskWorker', () => { - const logger = mockServices.logger.mock(); - const databases = TestDatabases.create(); - const testScopedSignal = createTestScopedSignal(); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it.each(databases.eachSupportedId())( - 'goes through the expected states, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const fn = jest.fn( - async () => new Promise(resolve => setTimeout(resolve, 50)), - ); - const settings: TaskSettingsV2 = { - version: 2, - cadence: '*/2 * * * * *', - initialDelayDuration: Duration.fromObject({ seconds: 1 }).toISO()!, - timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, - }; - - const worker = new TaskWorker('task1', fn, knex, logger); - await worker.persistTask(settings); - - let row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: null, - current_run_started_at: null, - current_run_expires_at: null, - }), - ); - expect(JSON.parse(row.settings_json)).toEqual({ - version: 2, - cadence: '*/2 * * * * *', - initialDelayDuration: 'PT1S', - timeoutAfterDuration: 'PT1M', - }); - - await expect(worker.findReadyTask()).resolves.toEqual({ - result: 'not-ready-yet', - }); - - await waitForExpect(async () => { - await expect(worker.findReadyTask()).resolves.toEqual({ - result: 'ready', - settings, - }); - }); - - row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: null, - current_run_started_at: null, - current_run_expires_at: null, - }), - ); - - await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); - - row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: 'ticket', - current_run_started_at: expect.anything(), - current_run_expires_at: expect.anything(), - }), - ); - - await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( - true, - ); - - row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: null, - current_run_started_at: null, - current_run_expires_at: null, - }), - ); - }, - ); - - it.each(databases.eachSupportedId())( - 'logs error when the task throws, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - jest.spyOn(logger, 'error'); - const fn = jest.fn().mockRejectedValue(new Error('failed')); - const settings: TaskSettingsV2 = { - version: 2, - initialDelayDuration: undefined, - cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, - }; - const checkFrequency = Duration.fromObject({ milliseconds: 100 }); - const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); - worker.start(settings, { signal: testScopedSignal() }); - - await waitForExpect(() => { - expect(logger.error).toHaveBeenCalled(); - }); - }, - ); - - it.each(databases.eachSupportedId())( - 'runs tasks more than once even when the task throws, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const fn = jest.fn().mockRejectedValue(new Error('failed')); - const settings: TaskSettingsV2 = { - version: 2, - initialDelayDuration: undefined, - cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, - }; - const checkFrequency = Duration.fromObject({ milliseconds: 100 }); - const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); - worker.start(settings, { signal: testScopedSignal() }); - - await waitForExpect(() => { - expect(fn).toHaveBeenCalledTimes(3); - }); - }, - ); - - it.each(databases.eachSupportedId())( - 'does not clobber ticket lock when stolen, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const fn = jest.fn( - async () => new Promise(resolve => setTimeout(resolve, 50)), - ); - const settings: TaskSettingsV2 = { - version: 2, - initialDelayDuration: undefined, - cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, - }; - - const worker = new TaskWorker('task1', fn, knex, logger); - await worker.persistTask(settings); - - await waitForExpect(async () => { - await expect(worker.findReadyTask()).resolves.toEqual({ - result: 'ready', - settings, - }); - }); - - await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); - - let row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: 'ticket', - current_run_started_at: expect.anything(), - current_run_expires_at: expect.anything(), - }), - ); - - await knex(DB_TASKS_TABLE) - .where('id', '=', 'task1') - .update({ current_run_ticket: 'stolen' }); - - await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( - false, - ); - - row = (await knex(DB_TASKS_TABLE))[0]; - expect(row).toEqual( - expect.objectContaining({ - id: 'task1', - current_run_ticket: 'stolen', - current_run_started_at: expect.anything(), - current_run_expires_at: expect.anything(), - }), - ); - }, - ); - - it.each(databases.eachSupportedId())( - 'gracefully handles a disappeared task row, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const fn = jest.fn(async () => {}); - const settings: TaskSettingsV2 = { - version: 2, - initialDelayDuration: undefined, - cadence: '* * * * * *', - timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, - }; - - const worker1 = new TaskWorker('task1', fn, knex, logger); - await worker1.persistTask(settings); - await knex(DB_TASKS_TABLE).where('id', '=', 'task1').delete(); - await expect(worker1.findReadyTask()).resolves.toEqual({ - result: 'abort', - }); - - const worker2 = new TaskWorker('task2', fn, knex, logger); - await worker2.persistTask(settings); - - await waitForExpect(async () => { - await expect(worker2.findReadyTask()).resolves.toEqual({ - result: 'ready', - settings, - }); - }); - - await knex(DB_TASKS_TABLE).where('id', '=', 'task2').delete(); - await expect(worker2.tryClaimTask('ticket', settings)).resolves.toBe( - false, - ); - - const worker3 = new TaskWorker('task3', fn, knex, logger); - await worker3.persistTask(settings); - - await waitForExpect(async () => { - await expect(worker3.findReadyTask()).resolves.toEqual({ - result: 'ready', - settings, - }); - }); - - await expect(worker3.tryClaimTask('ticket', settings)).resolves.toBe( - true, - ); - await knex(DB_TASKS_TABLE).where('id', '=', 'task3').delete(); - await expect(worker3.tryReleaseTask('ticket', settings)).resolves.toBe( - false, - ); - }, - ); - - it.each(databases.eachSupportedId())( - 'respects initialDelayDuration per worker, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const abortFirst = new AbortController(); - const settings: TaskSettingsV2 = { - version: 2, - initialDelayDuration: 'PT0.3S', - cadence: 'PT0.1S', - timeoutAfterDuration: 'PT10S', - }; - - // Start a single worker and make sure it waits and then goes to work - const fn1 = jest.fn(async () => {}); - const worker1 = new TaskWorker( - 'task1', - fn1, - knex, - logger, - Duration.fromMillis(10), - ); - await worker1.start(settings, { signal: abortFirst.signal }); - - expect(fn1).toHaveBeenCalledTimes(0); - await new Promise(resolve => setTimeout(resolve, 250)); - expect(fn1).toHaveBeenCalledTimes(0); - await new Promise(resolve => setTimeout(resolve, 100)); - expect(fn1.mock.calls.length).toBeGreaterThan(0); - - // Start a second worker and make sure it waits but the first worker still works along - const fn2 = jest.fn(); - const promise2 = new Promise(resolve => fn2.mockImplementation(resolve)); - const worker2 = new TaskWorker( - 'task1', - fn2, - knex, - logger, - Duration.fromMillis(10), - ); - await worker2.start(settings, { signal: testScopedSignal() }); - - // We eventually abort the first worker just to make sure that the second - // one for sure will get a go at running the task - setTimeout(() => abortFirst.abort(), 1000); - - const before = fn1.mock.calls.length; - await promise2; - expect(fn1.mock.calls.length).toBeGreaterThan(before); - - await knex.destroy(); - }, - ); - - it.each(databases.eachSupportedId())( - 'next_run_start_at is always the min between schedule changes from cron frequency, %p', - async databaseId => { - const knex = await databases.init(databaseId); - await migrateBackendTasks(knex); - - const fn = jest.fn( - async () => new Promise(resolve => setTimeout(resolve, 50)), - ); - const settings: TaskSettingsV2 = { - version: 2, - cadence: '*/15 * * * *', - initialDelayDuration: 'PT2M', - timeoutAfterDuration: 'PT1M', - }; - - const worker = new TaskWorker('task99', fn, knex, logger); - await worker.persistTask(settings); - const row1 = (await knex(DB_TASKS_TABLE))[0]; - - const settings2 = { - ...settings, - cadence: '*/2 * * * *', - initialDelayDuration: 'PT1M', - }; - await worker.persistTask(settings2); - const row2 = (await knex(DB_TASKS_TABLE))[0]; - - expect(row2.next_run_start_at).not.toStrictEqual(row1.next_run_start_at); - - const settings3 = { ...settings }; - await worker.persistTask(settings3); - const row3 = (await knex(DB_TASKS_TABLE))[0]; - - // The new timestamp can basically be 0 or a minute depending on how the - // initialDelayDuration falls right on a cron boundary. This kinda - // contrived check removes a test flakiness based on wall clock time. - expect( - Math.abs( - +new Date(row3.next_run_start_at) - +new Date(row2.next_run_start_at), - ), - ).toBeLessThanOrEqual(60_000); - - await knex.destroy(); - }, - ); - - it.each(databases.eachSupportedId())( - 'next_run_start_at is always the min between schedule changes when using human duration frequency, %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: 'PT120M', - timeoutAfterDuration: 'PT1M', - }; - - const worker = new TaskWorker('task99', fn, knex, logger); - await worker.persistTask(initialSettings); - // replicate task running, sets next_run_start_at based on cadence - await worker.tryClaimTask('ticket', initialSettings); - await worker.tryReleaseTask('ticket', initialSettings); - - // grab initial row for comparisons later - const rowAfterClaimAndRelease = ( - await knex(DB_TASKS_TABLE) - )[0]; - - const settings: TaskSettingsV2 = { - ...initialSettings, - cadence: 'PT60M', - }; - await worker.persistTask(settings); - const row1 = (await knex(DB_TASKS_TABLE))[0]; - - const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate( - new Date(rowAfterClaimAndRelease.next_run_start_at), - ); - const row1NextStartAt = DateTime.fromJSDate( - new Date(row1.next_run_start_at), - ); - const now = DateTime.now(); - expect( - rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'), - ).toBeCloseTo(60, 1); // ensure that next start at is sooner than initial by one hour - expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour - expect( - rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'), - ).toBeCloseTo(120, 1); - - const settings2 = { - ...settings, - }; - await worker.persistTask(settings2); - const row2 = (await knex(DB_TASKS_TABLE))[0]; - - expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at); - - await knex.destroy(); - }, - ); - - it.each(databases.eachSupportedId())( - 'next_run_start_at is always the min between schedule changes when using human duration frequency with initial start delay, %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: 'PT120M', - initialDelayDuration: 'PT2M', - timeoutAfterDuration: 'PT1M', - }; - - const worker = new TaskWorker('task99', fn, knex, logger); - await worker.persistTask(initialSettings); - // replicate task running, sets next_run_start_at based on cadence - await worker.tryClaimTask('ticket', initialSettings); - await worker.tryReleaseTask('ticket', initialSettings); - - // grab initial row for comparisons later - const rowAfterClaimAndRelease = ( - await knex(DB_TASKS_TABLE) - )[0]; - - const settings: TaskSettingsV2 = { - ...initialSettings, - cadence: 'PT60M', - }; - await worker.persistTask(settings); - const row1 = (await knex(DB_TASKS_TABLE))[0]; - - const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate( - new Date(rowAfterClaimAndRelease.next_run_start_at), - ); - const row1NextStartAt = DateTime.fromJSDate( - new Date(row1.next_run_start_at), - ); - const now = DateTime.now(); - expect( - rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'), - ).toBeCloseTo(62, 1); // ensure that next start at is sooner than initial by one hour, plus the 2 minute delay (set my tryReleaseTask) - expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour (2 minute delay doesn't take effect here) - expect( - rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'), - ).toBeCloseTo(122, 1); // includes 2 minute start delay (which is persisted from tryReleaseTask) - - const settings2 = { - ...settings, - }; - await worker.persistTask(settings2); - const row2 = (await knex(DB_TASKS_TABLE))[0]; - - expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at); - - 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 deleted file mode 100644 index a50b988522..0000000000 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConflictError, NotFoundError } from '@backstage/errors'; -import { CronTime } from 'cron'; -import { Knex } from 'knex'; -import { DateTime, Duration } from 'luxon'; -import { v4 as uuid } from 'uuid'; -import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; -import { TaskFunction, TaskSettingsV2, taskSettingsV2Schema } from './types'; -import { delegateAbortController, nowPlus, sleep } from './util'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -const DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); - -/** - * Implements tasks that run across worker hosts, with collaborative locking. - * - * @private - */ -export class TaskWorker { - constructor( - private readonly taskId: string, - private readonly fn: TaskFunction, - private readonly knex: Knex, - private readonly logger: LoggerService, - private readonly workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY, - ) {} - - async start(settings: TaskSettingsV2, options?: { signal?: AbortSignal }) { - try { - await this.persistTask(settings); - } catch (e) { - throw new Error(`Failed to persist task, ${e}`); - } - - this.logger.info( - `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`, - ); - - let workCheckFrequency = this.workCheckFrequency; - const isDuration = settings?.cadence.startsWith('P'); - if (isDuration) { - const cadence = Duration.fromISO(settings.cadence); - if (cadence < workCheckFrequency) { - workCheckFrequency = cadence; - } - } - - let attemptNum = 1; - (async () => { - for (;;) { - try { - if (settings.initialDelayDuration) { - await sleep( - Duration.fromISO(settings.initialDelayDuration), - options?.signal, - ); - } - - while (!options?.signal?.aborted) { - const runResult = await this.runOnce(options?.signal); - - if (runResult.result === 'abort') { - break; - } - - await sleep(workCheckFrequency, options?.signal); - } - - this.logger.info(`Task worker finished: ${this.taskId}`); - attemptNum = 0; - break; - } catch (e) { - attemptNum += 1; - this.logger.warn( - `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`, - ); - await sleep(Duration.fromObject({ seconds: 1 })); - } - } - })(); - } - - static async trigger(knex: Knex, taskId: string): Promise { - // check if task exists - const rows = await knex(DB_TASKS_TABLE) - .select(knex.raw(1)) - .where('id', '=', taskId); - if (rows.length !== 1) { - throw new NotFoundError(`Task ${taskId} does not exist`); - } - - const updatedRows = await knex(DB_TASKS_TABLE) - .where('id', '=', taskId) - .whereNull('current_run_ticket') - .update({ - next_run_start_at: knex.fn.now(), - }); - if (updatedRows < 1) { - throw new ConflictError(`Task ${taskId} is currently running`); - } - } - - /** - * Makes a single attempt at running the task to completion, if ready. - * - * @returns The outcome of the attempt - */ - private async runOnce( - signal?: AbortSignal, - ): Promise< - | { result: 'not-ready-yet' } - | { result: 'abort' } - | { result: 'failed' } - | { result: 'completed' } - > { - const findResult = await this.findReadyTask(); - if ( - findResult.result === 'not-ready-yet' || - findResult.result === 'abort' - ) { - return findResult; - } - - const taskSettings = findResult.settings; - const ticket = uuid(); - - const claimed = await this.tryClaimTask(ticket, taskSettings); - if (!claimed) { - return { result: 'not-ready-yet' }; - } - - // Abort the task execution either if the worker is stopped, or if the - // task timeout is hit - const taskAbortController = delegateAbortController(signal); - const timeoutHandle = setTimeout(() => { - taskAbortController.abort(); - }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds')); - - try { - await this.fn(taskAbortController.signal); - taskAbortController.abort(); // releases resources - } catch (e) { - this.logger.error(e); - await this.tryReleaseTask(ticket, taskSettings); - return { result: 'failed' }; - } finally { - clearTimeout(timeoutHandle); - } - - await this.tryReleaseTask(ticket, taskSettings); - return { result: 'completed' }; - } - - /** - * Perform the initial store of the task info - */ - async persistTask(settings: TaskSettingsV2) { - // Perform an initial parse to ensure that we will definitely be able to - // read it back again. - taskSettingsV2Schema.parse(settings); - - 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; - if (settings.initialDelayDuration) { - startAt = nowPlus( - Duration.fromISO(settings.initialDelayDuration), - this.knex, - ); - } - - if (isCron) { - const time = new CronTime(settings.cadence) - .sendAt() - .minus({ seconds: 1 }) // immediately, if "* * * * * *" - .toUTC(); - - 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); - } - - this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`); - - // It's OK if the task already exists; if it does, just replace its - // settings with the new value and start the loop as usual. - const settingsJson = JSON.stringify(settings); - await this.knex(DB_TASKS_TABLE) - .insert({ - id: this.taskId, - settings_json: settingsJson, - next_run_start_at: startAt, - }) - .onConflict('id') - .merge( - this.knex.client.config.client.includes('mysql') - ? { - settings_json: settingsJson, - next_run_start_at: this.knex.raw( - `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, - [ - nextStartAt, - 'next_run_start_at', - nextStartAt, - 'next_run_start_at', - ], - ), - } - : { - settings_json: this.knex.ref('excluded.settings_json'), - next_run_start_at: this.knex.raw( - `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, - [ - nextStartAt, - `${DB_TASKS_TABLE}.next_run_start_at`, - nextStartAt, - `${DB_TASKS_TABLE}.next_run_start_at`, - ], - ), - }, - ); - } - - /** - * Check if the task is ready to run - */ - async findReadyTask(): Promise< - | { result: 'not-ready-yet' } - | { result: 'abort' } - | { result: 'ready'; settings: TaskSettingsV2 } - > { - const [row] = await this.knex(DB_TASKS_TABLE) - .where('id', '=', this.taskId) - .select({ - settingsJson: 'settings_json', - ready: this.knex.raw( - `CASE - WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE - ELSE FALSE - END`, - [this.knex.fn.now()], - ), - }); - - if (!row) { - this.logger.info( - 'No longer able to find task; aborting and assuming that it has been unregistered or expired', - ); - return { result: 'abort' }; - } else if (!row.ready) { - return { result: 'not-ready-yet' }; - } - - try { - const obj = JSON.parse(row.settingsJson); - const settings = taskSettingsV2Schema.parse(obj); - return { result: 'ready', settings }; - } catch (e) { - this.logger.info( - `Task "${this.taskId}" is no longer able to parse task settings; aborting and assuming that a ` + - `newer version of the task has been issued and being handled by other workers, ${e}`, - ); - return { result: 'abort' }; - } - } - - /** - * Attempts to claim a task that's ready for execution, on this worker's - * behalf. We should not attempt to perform the work unless the claim really - * goes through. - * - * @param ticket - A globally unique string that changes for each invocation - * @param settings - The settings of the task to claim - * @returns True if it was successfully claimed - */ - async tryClaimTask( - ticket: string, - settings: TaskSettingsV2, - ): Promise { - const startedAt = this.knex.fn.now(); - const expiresAt = settings.timeoutAfterDuration - ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex) - : this.knex.raw('null'); - - const rows = await this.knex(DB_TASKS_TABLE) - .where('id', '=', this.taskId) - .whereNull('current_run_ticket') - .update({ - current_run_ticket: ticket, - current_run_started_at: startedAt, - current_run_expires_at: expiresAt, - }); - - return rows === 1; - } - - async tryReleaseTask( - ticket: string, - settings: TaskSettingsV2, - ): Promise { - const isManual = settings?.cadence === 'manual'; - const isDuration = settings?.cadence.startsWith('P'); - const isCron = !isManual && !isDuration; - - let nextRun: Knex.Raw; - if (isCron) { - const time = new CronTime(settings.cadence).sendAt().toUTC(); - 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( - `task: ${this.taskId} will next occur around ${DateTime.now().plus({ - seconds: dt, - })}`, - ); - - if (this.knex.client.config.client.includes('sqlite3')) { - nextRun = this.knex.raw( - `max(datetime(next_run_start_at, ?), datetime('now'))`, - [`+${dt} seconds`], - ); - } else if (this.knex.client.config.client.includes('mysql')) { - nextRun = this.knex.raw( - `greatest(next_run_start_at + interval ${dt} second, now())`, - ); - } else { - nextRun = this.knex.raw( - `greatest(next_run_start_at + interval '${dt} seconds', now())`, - ); - } - } - - const rows = await this.knex(DB_TASKS_TABLE) - .where('id', '=', this.taskId) - .where('current_run_ticket', '=', ticket) - .update({ - next_run_start_at: nextRun, - current_run_ticket: this.knex.raw('null'), - current_run_started_at: this.knex.raw('null'), - current_run_expires_at: this.knex.raw('null'), - }); - - return rows === 1; - } - - private nextRunAtRaw(time: DateTime): Knex.Raw { - if (this.knex.client.config.client.includes('sqlite3')) { - return this.knex.raw('datetime(?)', [time.toISO()]); - } else if (this.knex.client.config.client.includes('mysql')) { - return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]); - } - return this.knex.raw(`?`, [time.toISO()]); - } -} diff --git a/packages/backend-tasks/src/tasks/index.ts b/packages/backend-tasks/src/tasks/index.ts deleted file mode 100644 index 6f9ac6d859..0000000000 --- a/packages/backend-tasks/src/tasks/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; -export { TaskScheduler } from './TaskScheduler'; -export type { - PluginTaskScheduler, - TaskFunction, - TaskDescriptor, - TaskInvocationDefinition, - TaskRunner, - TaskScheduleDefinition, - TaskScheduleDefinitionConfig, -} from './types'; diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts deleted file mode 100644 index adeb134611..0000000000 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts +++ /dev/null @@ -1,131 +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 { ConfigReader } from '@backstage/config'; -import { HumanDuration } from '@backstage/types'; -import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; - -describe('readTaskScheduleDefinitionFromConfig', () => { - it('all valid values', () => { - const config = new ConfigReader({ - frequency: { - cron: '0 30 * * * *', - }, - timeout: 'PT3M', - initialDelay: { - minutes: 20, - }, - scope: 'global', - }); - - const result = readTaskScheduleDefinitionFromConfig(config); - - expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); - expect(result.timeout).toEqual({ minutes: 3 }); - expect((result.initialDelay as HumanDuration).minutes).toEqual(20); - expect(result.scope).toBe('global'); - }); - - it('all valid required values', () => { - const config = new ConfigReader({ - frequency: { - cron: '0 30 * * * *', - }, - timeout: 'PT3M', - }); - - const result = readTaskScheduleDefinitionFromConfig(config); - - expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); - expect(result.timeout).toEqual({ minutes: 3 }); - expect(result.initialDelay).toBeUndefined(); - expect(result.scope).toBeUndefined(); - }); - - it('fail without required frequency', () => { - const config = new ConfigReader({ - timeout: 'PT3M', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Missing required config value at 'frequency'", - ); - }); - - it('fail without required timeout', () => { - const config = new ConfigReader({ - frequency: 'PT30M', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Missing required config value at 'timeout'", - ); - }); - - it('invalid frequency key', () => { - const config = new ConfigReader({ - frequency: { - invalid: 'value', - }, - timeout: 'PT3M', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Failed to read duration from config at 'frequency', Error: Needs one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", - ); - }); - - it('invalid frequency value', () => { - const config = new ConfigReader({ - frequency: { - minutes: 'value', - }, - timeout: 'PT3M', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Failed to read duration from config, Error: Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number", - ); - }); - - it('frequency value with additional invalid prop', () => { - const config = new ConfigReader({ - frequency: { - minutes: 20, - invalid: 'value', - }, - timeout: 'PT3M', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Failed to read duration from config at 'frequency', Error: Unknown property 'invalid'; expected one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", - ); - }); - - it('invalid scope value', () => { - const config = new ConfigReader({ - frequency: { - years: 2, - }, - timeout: 'PT3M', - scope: 'invalid', - }); - - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - 'Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: invalid', - ); - }); -}); diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts deleted file mode 100644 index 85099d82db..0000000000 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ /dev/null @@ -1,85 +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 { Config, readDurationFromConfig } from '@backstage/config'; -import { HumanDuration } from '@backstage/types'; -import { TaskScheduleDefinition } from './types'; -import { Duration } from 'luxon'; - -function readDuration(config: Config, key: string): HumanDuration { - if (typeof config.get(key) === 'string') { - const value = config.getString(key); - const duration = Duration.fromISO(value); - if (!duration.isValid) { - throw new Error(`Invalid duration: ${value}`); - } - return duration.toObject(); - } - - return readDurationFromConfig(config, { key }); -} - -function readFrequency( - config: Config, - key: string, -): { 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); -} - -/** - * Reads a TaskScheduleDefinition from a Config. - * Expects the config not to be the root config, - * but the config for the definition. - * - * @param config - config for a TaskScheduleDefinition. - * @public - * @deprecated Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead - */ -export function readTaskScheduleDefinitionFromConfig( - config: Config, -): TaskScheduleDefinition { - const frequency = readFrequency(config, 'frequency'); - const timeout = readDuration(config, 'timeout'); - - const initialDelay = config.has('initialDelay') - ? readDuration(config, 'initialDelay') - : undefined; - - const scope = config.getOptionalString('scope'); - if (scope && !['global', 'local'].includes(scope)) { - throw new Error( - `Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`, - ); - } - - return { - frequency, - timeout, - initialDelay, - scope: scope as 'global' | 'local' | undefined, - }; -} diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts deleted file mode 100644 index a0bdbbaa64..0000000000 --- a/packages/backend-tasks/src/tasks/types.ts +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { HumanDuration, JsonObject } from '@backstage/types'; -import { CronTime } from 'cron'; -import { Duration } from 'luxon'; -import { z } from 'zod'; - -/** - * A function that can be called as a scheduled task. - * - * It may optionally accept an abort signal argument. When the signal triggers, - * processing should abort and return as quickly as possible. - * - * @public - * @deprecated Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead - */ -export type TaskFunction = - | ((abortSignal: AbortSignal) => void | Promise) - | (() => void | Promise); - -/** - * A semi-opaque type to describe an actively scheduled task. - * - * @public - * @deprecated Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead - */ -export type TaskDescriptor = { - /** - * The unique identifier of the task. - */ - id: string; - /** - * The scope of the task. - */ - scope: 'global' | 'local'; - /** - * The settings that control the task flow. This is a semi-opaque structure - * that is mainly there for debugging purposes. Do not make any assumptions - * about the contents of this field. - */ - settings: { version: number } & JsonObject; -}; - -/** - * Options that control the scheduling of a task. - * - * @public - * @deprecated Please import `SchedulerServiceTaskScheduleDefinition` from `@backstage/backend-plugin-api` instead - */ -export interface TaskScheduleDefinition { - /** - * How often you want the task to run. The system does its best to avoid - * overlapping invocations. - * - * @remarks - * - * This is the best effort value; under some circumstances there can be - * deviations. For example, if the task runtime is longer than the frequency - * and the timeout has not been given or not been exceeded yet, the next - * invocation of this task will be delayed until after the previous one - * finishes. - * - * This is a required field. - */ - frequency: - | { - /** - * A crontab style string. - * - * @remarks - * - * Overview: - * - * ``` - * ┌────────────── second (optional) - * │ ┌──────────── minute - * │ │ ┌────────── hour - * │ │ │ ┌──────── day of month - * │ │ │ │ ┌────── month - * │ │ │ │ │ ┌──── day of week - * │ │ │ │ │ │ - * │ │ │ │ │ │ - * * * * * * * - * ``` - */ - cron: string; - } - | Duration - | 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 - * it's considered timed out and gets "released" such that a new invocation - * is permitted to take place (possibly, then, on a different worker). - */ - timeout: Duration | HumanDuration; - - /** - * The amount of time that should pass before the first invocation happens. - * - * @remarks - * - * This can be useful in cold start scenarios to stagger or delay some heavy - * compute jobs. If no value is given for this field then the first invocation - * will happen as soon as possible according to the cadence. - * - * NOTE: This is a per-worker delay. If you have a cluster of workers all - * collaborating on a task that has its `scope` field set to `'global'`, then - * you may still see the task being processed by other long-lived workers, - * while any given single worker is in its initial sleep delay time e.g. after - * a deployment. Therefore, this parameter is not useful for "globally" pausing - * work; its main intended use is for individual machines to get a chance to - * reach some equilibrium at startup before triggering heavy batch workloads. - */ - initialDelay?: Duration | HumanDuration; - - /** - * Sets the scope of concurrency control / locking to apply for invocations of - * this task. - * - * @remarks - * - * When the scope is set to the default value `'global'`, the scheduler will - * attempt to ensure that only one worker machine runs the task at a time, - * according to the given cadence. This means that as the number of worker - * hosts increases, the invocation frequency of this task will not go up. - * Instead, the load is spread randomly across hosts. This setting is useful - * for tasks that access shared resources, for example catalog ingestion tasks - * where you do not want many machines to repeatedly import the same data and - * trample over each other. - * - * When the scope is set to `'local'`, there is no concurrency control across - * hosts. Each host runs the task according to the given cadence similarly to - * `setInterval`, but the runtime ensures that there are no overlapping runs. - * - * @defaultValue 'global' - */ - scope?: 'global' | 'local'; -} - -/** - * Config options for {@link TaskScheduleDefinition} - * that control the scheduling of a task. - * - * @public - * @deprecated Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead - */ -export interface TaskScheduleDefinitionConfig { - /** - * How often you want the task to run. The system does its best to avoid - * overlapping invocations. - * - * @remarks - * - * This is the best effort value; under some circumstances there can be - * deviations. For example, if the task runtime is longer than the frequency - * and the timeout has not been given or not been exceeded yet, the next - * invocation of this task will be delayed until after the previous one - * finishes. - * - * This is a required field. - */ - frequency: - | { - /** - * A crontab style string. - * - * @remarks - * - * Overview: - * - * ``` - * ┌────────────── second (optional) - * │ ┌──────────── minute - * │ │ ┌────────── hour - * │ │ │ ┌──────── day of month - * │ │ │ │ ┌────── month - * │ │ │ │ │ ┌──── day of week - * │ │ │ │ │ │ - * │ │ │ │ │ │ - * * * * * * * - * ``` - */ - cron: string; - } - | string - | HumanDuration; - - /** - * The maximum amount of time that a single task invocation can take, before - * it's considered timed out and gets "released" such that a new invocation - * is permitted to take place (possibly, then, on a different worker). - */ - timeout: string | HumanDuration; - - /** - * The amount of time that should pass before the first invocation happens. - * - * @remarks - * - * This can be useful in cold start scenarios to stagger or delay some heavy - * compute jobs. If no value is given for this field then the first invocation - * will happen as soon as possible according to the cadence. - * - * NOTE: This is a per-worker delay. If you have a cluster of workers all - * collaborating on a task that has its `scope` field set to `'global'`, then - * you may still see the task being processed by other long-lived workers, - * while any given single worker is in its initial sleep delay time e.g. after - * a deployment. Therefore, this parameter is not useful for "globally" pausing - * work; its main intended use is for individual machines to get a chance to - * reach some equilibrium at startup before triggering heavy batch workloads. - */ - initialDelay?: string | HumanDuration; - - /** - * Sets the scope of concurrency control / locking to apply for invocations of - * this task. - * - * @remarks - * - * When the scope is set to the default value `'global'`, the scheduler will - * attempt to ensure that only one worker machine runs the task at a time, - * according to the given cadence. This means that as the number of worker - * hosts increases, the invocation frequency of this task will not go up. - * Instead, the load is spread randomly across hosts. This setting is useful - * for tasks that access shared resources, for example catalog ingestion tasks - * where you do not want many machines to repeatedly import the same data and - * trample over each other. - * - * When the scope is set to `'local'`, there is no concurrency control across - * hosts. Each host runs the task according to the given cadence similarly to - * `setInterval`, but the runtime ensures that there are no overlapping runs. - * - * @defaultValue 'global' - */ - scope?: 'global' | 'local'; -} - -/** - * Options that apply to the invocation of a given task. - * - * @public - * @deprecated Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead - */ -export interface TaskInvocationDefinition { - /** - * A unique ID (within the scope of the plugin) for the task. - */ - id: string; - - /** - * The actual task function to be invoked regularly. - */ - fn: TaskFunction; - - /** - * An abort signal that, when triggered, will stop the recurring execution of - * the task. - */ - signal?: AbortSignal; -} - -/** - * A previously prepared task schedule, ready to be invoked. - * - * @public - * @deprecated Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead - */ -export interface TaskRunner { - /** - * Takes the schedule and executes an actual task using it. - * - * @param task - The actual runtime properties of the task - */ - run(task: TaskInvocationDefinition): Promise; -} - -/** - * Deals with the scheduling of distributed tasks, for a given plugin. - * - * @public - * @deprecated Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`) - */ -export interface PluginTaskScheduler { - /** - * Manually triggers a task by ID. - * - * If the task doesn't exist, a NotFoundError is thrown. If the task is - * currently running, a ConflictError is thrown. - * - * @param id - The task ID - */ - triggerTask(id: string): Promise; - - /** - * Schedules a task function for recurring runs. - * - * @remarks - * - * The `scope` task field controls whether to use coordinated exclusive - * invocation across workers, or to just coordinate within the current worker. - * - * This convenience method performs both the scheduling and invocation in one - * go. - * - * @param task - The task definition - */ - scheduleTask( - task: TaskScheduleDefinition & TaskInvocationDefinition, - ): Promise; - - /** - * Creates a scheduled but dormant recurring task, ready to be launched at a - * later time. - * - * @remarks - * - * This method is useful for pre-creating a schedule in outer code to be - * passed into an inner implementation, such that the outer code controls - * scheduling while inner code controls implementation. - * - * @param schedule - The task schedule - */ - createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; - - /** - * Returns all scheduled tasks registered to this scheduler. - * - * @remarks - * - * This method is useful for triggering tasks manually using the triggerTask - * functionality. Note that the returned tasks contain only tasks that have - * been initialized in this instance of the scheduler. - * - * @returns Scheduled tasks - */ - getScheduledTasks(): Promise; -} - -function isValidOptionalDurationString(d: string | undefined): boolean { - try { - return !d || Duration.fromISO(d).isValid; - } catch { - return false; - } -} - -function isValidCronFormat(c: string | undefined): boolean { - try { - if (!c) { - return false; - } - // parse cron format to ensure it's a valid format. - // eslint-disable-next-line no-new - new CronTime(c); - return true; - } catch { - return false; - } -} - -function isValidTrigger(t: string): boolean { - return t === 'manual'; -} - -export const taskSettingsV1Schema = z.object({ - version: z.literal(1), - initialDelayDuration: z - .string() - .optional() - .refine(isValidOptionalDurationString, { - message: 'Invalid duration, expecting ISO Period', - }), - recurringAtMostEveryDuration: z - .string() - .refine(isValidOptionalDurationString, { - message: 'Invalid duration, expecting ISO Period', - }), - timeoutAfterDuration: z.string().refine(isValidOptionalDurationString, { - message: 'Invalid duration, expecting ISO Period', - }), -}); - -/** - * The properties that control a scheduled task (version 1). - */ -export type TaskSettingsV1 = z.infer; - -export const taskSettingsV2Schema = z.object({ - version: z.literal(2), - 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', - }), - ), - timeoutAfterDuration: z.string().refine(isValidOptionalDurationString, { - message: 'Invalid duration, expecting ISO Period', - }), - initialDelayDuration: z - .string() - .optional() - .refine(isValidOptionalDurationString, { - message: 'Invalid duration, expecting ISO Period', - }), -}); - -/** - * The properties that control a scheduled task (version 2). - */ -export type TaskSettingsV2 = z.infer; diff --git a/packages/backend-tasks/src/tasks/util.test.ts b/packages/backend-tasks/src/tasks/util.test.ts deleted file mode 100644 index e2536abb88..0000000000 --- a/packages/backend-tasks/src/tasks/util.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import knexFactory, { Knex } from 'knex'; -import { Duration } from 'luxon'; -import { delegateAbortController, nowPlus, sleep, validateId } from './util'; - -class KnexBuilder { - public build(client: string): Knex { - return knexFactory({ client, useNullAsDefault: true }); - } -} - -describe('util', () => { - describe('validateId', () => { - it.each(['a', 'a_b', 'ab123c_2', 'a!', 'A', 'a-b', 'a.b', '_a', 'a_'])( - 'accepts valid inputs, %p', - async input => { - expect(validateId(input)).toBeUndefined(); - }, - ); - - it.each(['', null, Symbol('a')])( - 'rejects invalid inputs, %p', - async input => { - expect(() => validateId(input as any)).toThrow(); - }, - ); - }); - - describe('sleep', () => { - it('finishes the wait as expected with no signal', async () => { - const ac = new AbortController(); - const start = Date.now(); - await sleep(Duration.fromObject({ seconds: 1 }), ac.signal); - expect(Date.now() - start).toBeGreaterThan(800); - }, 5_000); - - it('aborts properly on the signal', async () => { - const ac = new AbortController(); - const promise = sleep(Duration.fromObject({ seconds: 10 }), ac.signal); - ac.abort(); - await promise; - expect(true).toBe(true); - }, 1_000); - }); - - describe('delegateAbortController', () => { - it('inherits parent abort state', () => { - const parent = new AbortController(); - const child = delegateAbortController(parent.signal); - expect(parent.signal.aborted).toBe(false); - expect(child.signal.aborted).toBe(false); - parent.abort(); - expect(parent.signal.aborted).toBe(true); - expect(child.signal.aborted).toBe(true); - }); - - it('does not inherit from child to parent', () => { - const parent = new AbortController(); - const child = delegateAbortController(parent.signal); - expect(parent.signal.aborted).toBe(false); - expect(child.signal.aborted).toBe(false); - child.abort(); - expect(parent.signal.aborted).toBe(false); - expect(child.signal.aborted).toBe(true); - }); - }); - - describe('nowPlus', () => { - describe('without duration', () => { - const databases = [ - { client: 'sqlite3', expected: 'CURRENT_TIMESTAMP' }, - { client: 'mysql2', expected: 'CURRENT_TIMESTAMP' }, - { client: 'pg', expected: 'CURRENT_TIMESTAMP' }, - ]; - - it.each(databases)('for client $client', ({ client, expected }) => { - const knex = new KnexBuilder().build(client); - const result = nowPlus(undefined, knex); - - expect(result.toString()).toBe(expected); - }); - }); - describe('With duration', () => { - const databases = [ - { client: 'sqlite3', expected: "datetime('now', '20 seconds')" }, - { client: 'mysql2', expected: 'now() + interval 20 second' }, - { client: 'pg', expected: "now() + interval '20 seconds'" }, - ]; - it.each(databases)('for client $client', ({ client, expected }) => { - const duration = Duration.fromObject({ seconds: 20 }); - const knex = new KnexBuilder().build(client); - const result = nowPlus(duration, knex); - - expect(result.toString()).toBe(expected); - }); - }); - }); -}); diff --git a/packages/backend-tasks/src/tasks/util.ts b/packages/backend-tasks/src/tasks/util.ts deleted file mode 100644 index 70d67a9fbe..0000000000 --- a/packages/backend-tasks/src/tasks/util.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { InputError } from '@backstage/errors'; -import { Knex } from 'knex'; -import { DateTime, Duration } from 'luxon'; - -// Keep the IDs compatible with e.g. Prometheus labels -export function validateId(id: string) { - if (typeof id !== 'string' || !id.trim()) { - throw new InputError(`${id} is not a valid ID, expected non-empty string`); - } -} - -export function dbTime(t: Date | string): DateTime { - if (typeof t === 'string') { - return DateTime.fromSQL(t); - } - return DateTime.fromJSDate(t); -} - -export function nowPlus(duration: Duration | undefined, knex: Knex) { - const seconds = duration?.as('seconds') ?? 0; - if (!seconds) { - return knex.fn.now(); - } - - if (knex.client.config.client.includes('sqlite3')) { - return knex.raw(`datetime('now', ?)`, [`${seconds} seconds`]); - } - - if (knex.client.config.client.includes('mysql')) { - return knex.raw(`now() + interval ${seconds} second`); - } - - return knex.raw(`now() + interval '${seconds} seconds'`); -} - -/** - * Sleep for the given duration, but return sooner if the abort signal - * triggers. - * - * @param duration - The amount of time to sleep, at most - * @param abortSignal - An optional abort signal that short circuits the wait - */ -export async function sleep( - duration: Duration, - abortSignal?: AbortSignal, -): Promise { - if (abortSignal?.aborted) { - return; - } - - await new Promise(resolve => { - let timeoutHandle: NodeJS.Timeout | undefined = undefined; - - const done = () => { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - abortSignal?.removeEventListener('abort', done); - resolve(); - }; - - timeoutHandle = setTimeout(done, duration.as('milliseconds')); - abortSignal?.addEventListener('abort', done); - }); -} - -/** - * Creates a new AbortController that, in addition to working as a regular - * standalone controller, also gets aborted if the given parent signal - * reaches aborted state. - * - * @param parent - The "parent" signal that can trigger the delegate - */ -export function delegateAbortController(parent?: AbortSignal): AbortController { - const delegate = new AbortController(); - - if (parent) { - if (parent.aborted) { - delegate.abort(); - } else { - const onParentAborted = () => { - delegate.abort(); - }; - - const onChildAborted = () => { - parent.removeEventListener('abort', onParentAborted); - }; - - parent.addEventListener('abort', onParentAborted, { once: true }); - delegate.signal.addEventListener('abort', onChildAborted, { once: true }); - } - } - - return delegate; -} diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index cbca6bdcab..28ee376101 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/backend-test-utils +## 0.6.0-next.1 + +### Patch Changes + +- 710f621: Added missing service mock for `mockServices.rootConfig.mock`, and fixed the definition of `mockServices.rootHttpRouter.factory` to not have a duplicate callback. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.6.0-next.0 + +### Minor Changes + +- 19ff127: **BREAKING**: Removed service mocks for the identity and token manager services, which have been removed from `@backstage/backend-plugin-api`. +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 0363bf1: There is a new `mockErrorHandler` utility to help in mocking the error middleware in tests. +- Updated dependencies + - @backstage/backend-app-api@0.10.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 584fedb1f5..2bea998596 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -3,8 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// /// /// +/// import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; @@ -18,29 +20,28 @@ import { BackstageUserPrincipal } from '@backstage/backend-plugin-api'; import { CacheService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { ErrorRequestHandler } from 'express'; 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 { 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'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { ParamsDictionary } from 'express-serve-static-core'; +import { ParsedQs } from 'qs'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootHealthService } from '@backstage/backend-plugin-api'; -import { RootHttpRouterFactoryOptions } from '@backstage/backend-defaults/rootHttpRouter'; 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 { ServiceFactory } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; -import { TokenManagerService } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; @@ -55,9 +56,6 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } -// @public @deprecated (undocumented) -export function isDockerDisabledForTests(): boolean; - // @public (undocumented) export namespace mockCredentials { export function limitedUser( @@ -143,8 +141,14 @@ export interface MockDirectoryContentOptions { shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean); } -// @public @deprecated (undocumented) -export type MockDirectoryOptions = CreateMockDirectoryOptions; +// @public +export function mockErrorHandler(): ErrorRequestHandler< + ParamsDictionary, + any, + any, + ParsedQs, + Record +>; // @public (undocumented) export namespace mockServices { @@ -156,12 +160,7 @@ export namespace mockServices { // (undocumented) export namespace auth { const // (undocumented) - factory: ServiceFactoryCompat< - AuthService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -170,12 +169,7 @@ export namespace mockServices { // (undocumented) export namespace cache { const // (undocumented) - factory: ServiceFactoryCompat< - CacheService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -184,12 +178,7 @@ export namespace mockServices { // (undocumented) export namespace database { const // (undocumented) - factory: ServiceFactoryCompat< - DatabaseService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -200,12 +189,7 @@ export namespace mockServices { // (undocumented) export namespace discovery { const // (undocumented) - factory: ServiceFactoryCompat< - DiscoveryService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -214,12 +198,7 @@ export namespace mockServices { // (undocumented) export namespace events { const // (undocumented) - factory: ServiceFactoryCompat< - EventsService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -231,10 +210,9 @@ export namespace mockServices { }): HttpAuthService; // (undocumented) export namespace httpAuth { - const factory: ((options?: { + const factory: (options?: { defaultCredentials?: BackstageCredentials; - }) => ServiceFactory) & - ServiceFactory; + }) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -243,42 +221,16 @@ export namespace mockServices { // (undocumented) export namespace httpRouter { const // (undocumented) - factory: ServiceFactoryCompat< - HttpRouterService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, ) => ServiceMock; } // (undocumented) - export function identity(): IdentityService; - // (undocumented) - export namespace identity { - const // (undocumented) - factory: ServiceFactoryCompat< - IdentityService, - 'plugin', - 'singleton', - undefined - >; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } - // (undocumented) export namespace lifecycle { const // (undocumented) - factory: ServiceFactoryCompat< - LifecycleService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -287,12 +239,7 @@ export namespace mockServices { // (undocumented) export namespace logger { const // (undocumented) - factory: ServiceFactoryCompat< - LoggerService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -301,12 +248,7 @@ export namespace mockServices { // (undocumented) export namespace permissions { const // (undocumented) - factory: ServiceFactoryCompat< - PermissionsService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -321,28 +263,18 @@ export namespace mockServices { data?: JsonObject; }; const // (undocumented) - factory: ServiceFactory< - RootConfigService, - 'root', - 'singleton' | 'multiton' - > & - (( - options?: Options | undefined, - ) => ServiceFactory< - RootConfigService, - 'root', - 'singleton' | 'multiton' - >); + factory: ( + options?: Options | undefined, + ) => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; } // (undocumented) export namespace rootHealth { const // (undocumented) - factory: ServiceFactoryCompat< - RootHealthService, - 'root', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -351,10 +283,7 @@ export namespace mockServices { // (undocumented) export namespace rootHttpRouter { const // (undocumented) - factory: (( - options?: RootHttpRouterFactoryOptions | undefined, - ) => ServiceFactory) & - ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -363,12 +292,7 @@ export namespace mockServices { // (undocumented) export namespace rootLifecycle { const // (undocumented) - factory: ServiceFactoryCompat< - RootLifecycleService, - 'root', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -383,10 +307,9 @@ export namespace mockServices { level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; const // (undocumented) - factory: ServiceFactory & - (( - options?: Options | undefined, - ) => ServiceFactory); + factory: ( + options?: Options | undefined, + ) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -395,42 +318,16 @@ export namespace mockServices { // (undocumented) export namespace scheduler { const // (undocumented) - factory: ServiceFactoryCompat< - SchedulerService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, ) => ServiceMock; } // (undocumented) - export function tokenManager(): TokenManagerService; - // (undocumented) - export namespace tokenManager { - const // (undocumented) - factory: ServiceFactoryCompat< - TokenManagerService, - 'plugin', - 'singleton', - undefined - >; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } - // (undocumented) export namespace urlReader { const // (undocumented) - factory: ServiceFactoryCompat< - UrlReaderService, - 'plugin', - 'singleton', - undefined - >; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -441,12 +338,7 @@ export namespace mockServices { ): UserInfoService; // (undocumented) export namespace userInfo { - const factory: ServiceFactoryCompat< - UserInfoService, - 'plugin', - 'singleton', - undefined - >; + const factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -475,10 +367,6 @@ export class ServiceFactoryTester< subject: ServiceFactory, options?: ServiceFactoryTesterOptions, ): ServiceFactoryTester; - // @deprecated - get( - ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise; getService< TGetService, TGetScope extends 'root' | 'plugin', @@ -508,13 +396,6 @@ export type ServiceMock = { : TService[Key]; }; -// @public @deprecated (undocumented) -export function setupRequestMockHandlers(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}): void; - // @public (undocumented) export function startTestBackend( options: TestBackendOptions, diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 44373e733f..a84aca9b52 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.5-next.2", + "version": "0.6.0-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -55,7 +55,10 @@ "@backstage/types": "workspace:^", "@keyv/memcache": "^1.3.5", "@keyv/redis": "^2.5.3", + "@types/express": "^4.17.6", + "@types/express-serve-static-core": "^4.17.5", "@types/keyv": "^4.2.0", + "@types/qs": "^6.9.6", "better-sqlite3": "^11.0.0", "cookie": "^0.6.0", "express": "^4.17.1", diff --git a/packages/backend-test-utils/src/deprecated.ts b/packages/backend-test-utils/src/deprecated.ts deleted file mode 100644 index 9790580a9c..0000000000 --- a/packages/backend-test-utils/src/deprecated.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 './msw'; -import { CreateMockDirectoryOptions } from './filesystem'; -import { isDockerDisabledForTests as _isDockerDisabledForTests } from './util'; - -/** - * @public - * @deprecated Use `CreateMockDirectoryOptions` from `@backstage/backend-test-utils` instead. - */ -export type MockDirectoryOptions = CreateMockDirectoryOptions; - -/** - * @public - * @deprecated Use `registerMswTestHooks` from `@backstage/backend-test-utils` instead. - */ -export function setupRequestMockHandlers(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}): void { - registerMswTestHooks(worker); -} - -/** - * @public - * @deprecated This is an internal function and will no longer be exported from this package. - */ -export function isDockerDisabledForTests(): boolean { - return _isDockerDisabledForTests(); -} diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index 4124a861f4..d8e0e2c4a6 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -20,9 +20,9 @@ * @packageDocumentation */ -export * from './deprecated'; export * from './cache'; export * from './database'; export * from './msw'; export * from './filesystem'; export * from './next'; +export { mockErrorHandler } from './util'; 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.test.ts b/packages/backend-test-utils/src/next/services/mockServices.test.ts new file mode 100644 index 0000000000..80d6e4c6f1 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/mockServices.test.ts @@ -0,0 +1,32 @@ +/* + * 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 { mockServices } from './mockServices'; + +describe('mockServices', () => { + const coreServiceKeys = Object.keys(coreServices).filter( + key => key !== 'pluginMetadata', + ) as Array; + + it.each(coreServiceKeys)('should have mock implementations for %s', key => { + expect(mockServices[key]).toBeDefined(); + expect(mockServices[key].mock).toEqual(expect.any(Function)); + expect(mockServices[key].mock()).toEqual(expect.any(Object)); + expect(mockServices[key].factory).toEqual(expect.any(Function)); + expect(mockServices[key].factory()).toEqual(expect.any(Object)); + }); +}); diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 8d36fec50e..8b0f196399 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'; @@ -77,8 +74,7 @@ function simpleFactoryWithOptions< >( ref: ServiceRef, factory: (...options: TOptions) => TService, -): ServiceFactory & - ((...options: TOptions) => ServiceFactory) { +): (...options: TOptions) => ServiceFactory { const factoryWithOptions = (...options: TOptions) => createServiceFactory({ service: ref as ServiceRef, @@ -86,7 +82,7 @@ function simpleFactoryWithOptions< async factory() { return factory(...options); }, - })(); + }); return Object.assign( factoryWithOptions, factoryWithOptions(...([undefined] as unknown as TOptions)), @@ -126,7 +122,7 @@ function simpleMock( service: ref, deps: {}, factory: () => mock, - })(), + }), }) as ServiceMock; }; } @@ -145,6 +141,24 @@ export namespace mockServices { coreServices.rootConfig, rootConfig, ); + export const mock = simpleMock(coreServices.rootConfig, () => ({ + get: jest.fn(), + getBoolean: jest.fn(), + getConfig: jest.fn(), + getConfigArray: jest.fn(), + getNumber: jest.fn(), + getOptional: jest.fn(), + getOptionalBoolean: jest.fn(), + getOptionalConfig: jest.fn(), + getOptionalConfigArray: jest.fn(), + getOptionalNumber: jest.fn(), + getOptionalString: jest.fn(), + getOptionalStringArray: jest.fn(), + getString: jest.fn(), + getStringArray: jest.fn(), + has: jest.fn(), + keys: jest.fn(), + })); } export function rootLogger(options?: rootLogger.Options): LoggerService { @@ -168,44 +182,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 = createServiceFactory({ - service: coreServices.tokenManager, - deps: {}, - factory: () => 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 = createServiceFactory({ - service: coreServices.identity, - deps: {}, - factory: () => identity(), - }); - export const mock = simpleMock(coreServices.identity, () => ({ - getIdentity: jest.fn(), - })); - } - export function auth(options?: { pluginId?: string; disableDefaultAuthPolicy?: boolean; @@ -216,24 +192,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(), @@ -257,7 +234,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(), @@ -288,7 +265,14 @@ export namespace mockServices { ); } export namespace httpAuth { - const factoryWithOptions = (options?: { + /** + * Creates a mock service factory for the `HttpAuthService`. + * + * By default all requests without credentials are treated as requests from + * the default mock user principal. This behavior can be configured with the + * `defaultCredentials` option. + */ + export const factory = (options?: { defaultCredentials?: BackstageCredentials; }) => createServiceFactory({ @@ -299,18 +283,7 @@ export namespace mockServices { plugin.getId(), options?.defaultCredentials ?? mockCredentials.user(), ), - })(); - /** - * Creates a mock service factory for the `HttpAuthService`. - * - * By default all requests without credentials are treated as requests from - * the default mock user principal. This behavior can be configured with the - * `defaultCredentials` option. - */ - export const factory = Object.assign( - factoryWithOptions, - factoryWithOptions(), - ); + }); export const mock = simpleMock(coreServices.httpAuth, () => ({ credentials: jest.fn(), issueUserCookie: jest.fn(), @@ -336,13 +309,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(), })); @@ -352,7 +326,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(), @@ -362,14 +336,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(), @@ -377,7 +351,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(), @@ -385,14 +359,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(), @@ -400,15 +374,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(), @@ -416,7 +389,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(), @@ -424,7 +397,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(), @@ -434,7 +407,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(), @@ -443,7 +416,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 5970f6ea52..6690f76e76 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts +++ b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts @@ -93,7 +93,7 @@ describe('ServiceFactoryTester', () => { deps: { root: rootServiceRef, plugin: pluginServiceRef }, factory: async ({ root, plugin }) => `${root}, ${plugin}`, }), - { dependencies: [rootFactory, pluginFactory()] }, + { dependencies: [rootFactory, pluginFactory] }, ); await expect(tester.getSubject('x')).resolves.toBe('root, x-plugin'); @@ -106,7 +106,7 @@ describe('ServiceFactoryTester', () => { deps: { shared: sharedPluginServiceRef, plugin: pluginServiceRef }, factory: async ({ shared, plugin }) => `${shared}, ${plugin}`, }), - { dependencies: [sharedPluginFactory(), pluginFactory] }, + { dependencies: [sharedPluginFactory, pluginFactory] }, ); await expect(tester.getSubject('x')).resolves.toBe('x-1-plugin, x-plugin'); diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts index e8530f89bf..83a6a42307 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts +++ b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts @@ -82,17 +82,6 @@ export class ServiceFactoryTester< this.#registry = registry; } - /** - * Returns the service instance for the subject. - * - * @deprecated Use `getSubject` instead. - */ - async get( - ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise { - return this.getSubject(...args); - } - /** * Returns the service instance for the subject. * 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 a01d4bebf5..812e234828 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); }, }); @@ -335,4 +334,78 @@ describe('TestBackend', () => { "Unable to determine the plugin ID of extension point(s) 'a'. Tested extension points must be depended on by one or more tested modules.", ); }); + + it('should forward errors from plugins', async () => { + await expect( + startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('nah'); + }, + }); + }, + }), + ], + }), + ).rejects.toThrow("Plugin 'test' startup failed; caused by Error: nah"); + }); + + it('should forward errors from modules', async () => { + await expect( + startTestBackend({ + features: [ + createBackendModule({ + pluginId: 'test', + moduleId: 'tester', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('nah'); + }, + }); + }, + }), + ], + }), + ).rejects.toThrow( + "Module 'tester' for plugin 'test' startup failed; caused by Error: nah", + ); + }); + + it('should forward errors from plugin register', async () => { + await expect( + startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'test', + register() { + throw new Error('nah'); + }, + }), + ], + }), + ).rejects.toThrow('nah'); + }); + + it('should forward errors from module register', async () => { + await expect( + startTestBackend({ + features: [ + createBackendModule({ + pluginId: 'test', + moduleId: 'tester', + register() { + throw new Error('nah'); + }, + }), + ], + }), + ).rejects.toThrow('nah'); + }); }); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index cf98c25e4a..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, @@ -40,7 +32,14 @@ import { InternalBackendFeature, InternalBackendRegistrations, } from '@backstage/backend-plugin-api/src/wiring/types'; -import { createHealthRouter } from '@backstage/backend-defaults/rootHttpRouter'; +import { + DefaultRootHttpRouter, + ExtendedHttpServer, + MiddlewareFactory, + createHealthRouter, + createHttpServer, +} from '@backstage/backend-defaults/rootHttpRouter'; +import { HostDiscovery } from '@backstage/backend-defaults/discovery'; /** @public */ export interface TestBackendOptions { @@ -73,7 +72,6 @@ export const defaultServiceFactories = [ mockServices.database.factory(), mockServices.httpAuth.factory(), mockServices.httpRouter.factory(), - mockServices.identity.factory(), mockServices.lifecycle.factory(), mockServices.logger.factory(), mockServices.permissions.factory(), @@ -81,7 +79,6 @@ export const defaultServiceFactories = [ mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.scheduler.factory(), - mockServices.tokenManager.factory(), mockServices.userInfo.factory(), mockServices.urlReader.factory(), mockServices.events.factory(), diff --git a/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts b/packages/backend-test-utils/src/util/errorHandler.ts similarity index 63% rename from packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts rename to packages/backend-test-utils/src/util/errorHandler.ts index 746f480c41..f534823f50 100644 --- a/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts +++ b/packages/backend-test-utils/src/util/errorHandler.ts @@ -14,11 +14,16 @@ * limitations under the License. */ -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { userInfoServiceFactory as _userInfoServiceFactory } from '../../../../../backend-defaults/src/entrypoints/userInfo'; +import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; +import { mockServices } from '../next'; /** + * A mock for error handler middleware that can be used in router tests. * @public - * @deprecated Please import from `@backstage/backend-defaults/userInfo` instead. */ -export const userInfoServiceFactory = _userInfoServiceFactory; +export function mockErrorHandler() { + return MiddlewareFactory.create({ + config: mockServices.rootConfig(), + logger: mockServices.rootLogger(), + }).error(); +} diff --git a/packages/backend-test-utils/src/util/index.ts b/packages/backend-test-utils/src/util/index.ts index a6cdc621d4..621c36ba8f 100644 --- a/packages/backend-test-utils/src/util/index.ts +++ b/packages/backend-test-utils/src/util/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export { mockErrorHandler } from './errorHandler'; export { isDockerDisabledForTests } from './isDockerDisabledForTests'; diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 8fbce11752..e5555f42a0 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,150 @@ # example-backend +## 0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-notifications-backend@0.4.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + +## 0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.13-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend@1.5.17-next.0 + - @backstage/plugin-kubernetes-backend@0.18.6-next.0 + - @backstage/plugin-scaffolder-backend@1.25.0-next.0 + - @backstage/plugin-app-backend@0.3.74-next.0 + - @backstage/plugin-signals-backend@0.2.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-permission-backend@0.5.49-next.0 + - @backstage/plugin-proxy-backend@0.5.6-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.0 + - @backstage/plugin-devtools-backend@0.4.0-next.0 + - @backstage/plugin-notifications-backend@0.4.0-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-search-backend-node@1.3.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 cafe89a527..7f81417b31 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.29-next.2", + "version": "0.0.30-next.1", "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:^", @@ -58,9 +57,10 @@ "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-techdocs-backend": "workspace:^", - "@opentelemetry/auto-instrumentations-node": "^0.43.0", - "@opentelemetry/exporter-prometheus": "^0.50.0", - "@opentelemetry/sdk-node": "^0.50.0" + "@opentelemetry/auto-instrumentations-node": "^0.49.0", + "@opentelemetry/exporter-prometheus": "^0.53.0", + "@opentelemetry/sdk-node": "^0.53.0", + "example-app": "link:../app" }, "devDependencies": { "@backstage/cli": "workspace:^" diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index da7b16dce1..cf175a3d23 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/catalog-client +## 1.6.7-next.0 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 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..b0568e0b0d 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.7-next.0", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 2440f043d7..2e7912b69a 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -18,7 +18,6 @@ import { CompoundEntityRef, Entity, parseEntityRef, - stringifyEntityRef, stringifyLocationRef, } from '@backstage/catalog-model'; import { ResponseError } from '@backstage/errors'; @@ -142,35 +141,7 @@ export class CatalogClient implements CatalogApi { options, ), ); - - // do not sort entities, if order is provided - if (encodedOrder.length) { - return { items: entities }; - } - - const refCompare = (a: Entity, b: Entity) => { - // in case field filtering is used, these fields might not be part of the response - if ( - a.metadata?.name === undefined || - a.kind === undefined || - b.metadata?.name === undefined || - b.kind === undefined - ) { - return 0; - } - - const aRef = stringifyEntityRef(a); - const bRef = stringifyEntityRef(b); - if (aRef < bRef) { - return -1; - } - if (aRef > bRef) { - return 1; - } - return 0; - }; - - return { items: entities.sort(refCompare) }; + return { items: entities }; } /** 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/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/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 887445aac7..05a9574f3a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,104 @@ # @backstage/cli +## 0.27.1-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common +- f865103: Updated dependency `esbuild` to `^0.23.0`. +- 569c3f0: Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.27.1-next.0 + +### Patch Changes + +- 1b5c264: Add `checks: 'read'` for default GitHub app permissions +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 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 diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 96d8478ec0..856068eef9 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -277,7 +277,7 @@ function createConfigForRole(dir, role, extraConfig = {}) { restrictedSrcSyntax: [ { message: - "`__dirname` doesn't refer to the same dir in production builds, try `resolvePackagePath()` from `@backstage/backend-common` instead.", + "`__dirname` doesn't refer to the same dir in production builds, try `resolvePackagePath()` from `@backstage/backend-plugin-api` instead.", selector: 'Identifier[name="__dirname"]', }, ...(extraConfig.restrictedSrcSyntax ?? []), diff --git a/packages/cli/package.json b/packages/cli/package.json index ba45b8f514..31cfd3747e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.27.0-next.3", + "version": "0.27.1-next.1", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -53,7 +53,7 @@ "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@module-federation/enhanced": "^0.1.19", + "@module-federation/enhanced": "^0.3.5", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", @@ -91,7 +91,7 @@ "css-loader": "^6.5.1", "ctrlc-windows": "^2.1.0", "diff": "^5.0.0", - "esbuild": "^0.21.0", + "esbuild": "^0.23.0", "esbuild-loader": "^4.0.0", "eslint": "^8.6.0", "eslint-config-prettier": "^9.0.0", diff --git a/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts b/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts index 70b8c0ca53..b1825ad996 100644 --- a/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts +++ b/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts @@ -114,10 +114,16 @@ export class GithubCreateAppServer { default_events: ['create', 'delete', 'push', 'repository'], default_permissions: { metadata: 'read', - ...(this.permissions.includes('members') && { members: 'read' }), - ...(this.permissions.includes('read') && { contents: 'read' }), + ...(this.permissions.includes('members') && { + members: 'read', + }), + ...(this.permissions.includes('read') && { + contents: 'read', + checks: 'read', + }), ...(this.permissions.includes('write') && { contents: 'write', + checks: 'read', actions: 'write', }), }, diff --git a/packages/cli/src/commands/create-github-app/index.ts b/packages/cli/src/commands/create-github-app/index.ts index 6b3e732f18..681b471c91 100644 --- a/packages/cli/src/commands/create-github-app/index.ts +++ b/packages/cli/src/commands/create-github-app/index.ts @@ -22,6 +22,7 @@ import { paths } from '../../lib/paths'; import { GithubCreateAppServer } from './GithubCreateAppServer'; import fetch from 'node-fetch'; import openBrowser from 'react-dev-utils/openBrowser'; + // This is an experimental command that at this point does not support GitHub Enterprise // due to lacking support for creating apps from manifests. // https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app-from-a-manifest diff --git a/packages/cli/src/commands/repo/list-deprecations.ts b/packages/cli/src/commands/repo/list-deprecations.ts index 69fe51f78e..772038025a 100644 --- a/packages/cli/src/commands/repo/list-deprecations.ts +++ b/packages/cli/src/commands/repo/list-deprecations.ts @@ -17,7 +17,7 @@ import chalk from 'chalk'; import { ESLint } from 'eslint'; import { OptionValues } from 'commander'; -import { join as joinPath, relative as relativePath } from 'path'; +import { relative as relativePath } from 'path'; import { PackageGraph } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; @@ -45,7 +45,7 @@ export async function command(opts: OptionValues) { const deprecations = []; for (const [index, pkg] of packages.entries()) { - const results = await eslint.lintFiles(joinPath(pkg.dir, 'src')); + const results = await eslint.lintFiles(pkg.dir); for (const result of results) { for (const message of result.messages) { if (message.ruleId !== 'deprecation/deprecation') { diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 8beb2cbb1e..a514158e9b 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -118,6 +118,7 @@ export async function makeRollupConfigs( ), output, onwarn, + makeAbsoluteExternalsRelative: false, preserveEntrySignatures: 'strict', // All module imports are always marked as external external: (source, importer, isResolved) => diff --git a/packages/cli/templates/default-backend-module/package.json.hbs b/packages/cli/templates/default-backend-module/package.json.hbs index db8b8749b3..7e82e047ce 100644 --- a/packages/cli/templates/default-backend-module/package.json.hbs +++ b/packages/cli/templates/default-backend-module/package.json.hbs @@ -29,7 +29,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}" }, "devDependencies": { diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index b05d1a844b..6c25562c21 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -28,7 +28,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", "@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}", "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}", "express": "{{versionQuery 'express' '4.17.1'}}", diff --git a/packages/cli/templates/scaffolder-module/package.json.hbs b/packages/cli/templates/scaffolder-module/package.json.hbs index df4bf352f4..a6e6b5c5bc 100644 --- a/packages/cli/templates/scaffolder-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-module/package.json.hbs @@ -32,7 +32,6 @@ "@backstage/plugin-scaffolder-node": "{{versionQuery '@backstage/plugin-scaffolder-node'}}" }, "devDependencies": { - "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", "@backstage/cli": "{{versionQuery '@backstage/cli'}}" }, "files": [ diff --git a/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts b/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts index 242f93c2f3..d0e5374f3f 100644 --- a/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts +++ b/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts @@ -1,6 +1,5 @@ import { PassThrough } from 'stream'; import { createAcmeExampleAction } from './example'; -import { getVoidLogger } from '@backstage/backend-common'; describe('acme:example', () => { afterEach(() => { @@ -10,15 +9,14 @@ describe('acme:example', () => { it('should call action', async () => { const action = createAcmeExampleAction(); - const logger = getVoidLogger(); - jest.spyOn(logger, 'info'); + const logger = { info: jest.fn() }; await action.handler({ input: { myParameter: 'test', }, workspacePath: '/tmp', - logger, + logger: logger as any, logStream: new PassThrough(), output: jest.fn(), createTemporaryDirectory() { diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 2a47e34b54..c93a71fe38 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/codemods +## 0.1.50-next.0 + +### Patch Changes + +- 0894166: Updated dependency `jscodeshift` to `^0.16.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + ## 0.1.49 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 6bab6f3df8..38d32dfc74 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/codemods", - "version": "0.1.49", + "version": "0.1.50-next.0", "description": "A collection of codemods for Backstage projects", "backstage": { "role": "cli" @@ -46,7 +46,7 @@ "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", "commander": "^12.0.0", - "jscodeshift": "^0.15.0", + "jscodeshift": "^0.16.0", "jscodeshift-add-imports": "^1.0.10" }, "devDependencies": { diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 78f2123970..a60e86a6c8 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,24 @@ # @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 diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index a47a17c12f..98d233b348 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.9.0-next.2", + "version": "1.9.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 26b9eb20fe..2ae26f8b97 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 6a107e0a87..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.14.2-next.0", + "version": "1.14.2", "publishConfig": { "access": "public" }, diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 34c134f731..62a77898bd 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/core-compat-api +## 0.3.0-next.1 + +### Minor Changes + +- 6db849e: **BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 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 diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 8d177b4dab..ddb3bc6bf4 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -8,9 +8,14 @@ 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 { ComponentType } from 'react'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendModule } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -24,7 +29,24 @@ export function compatWrapper(element: ReactNode): React_2.JSX.Element; // @public (undocumented) export function convertLegacyApp( rootElement: React_2.JSX.Element, -): FrontendFeature[]; +): (FrontendPlugin | FrontendModule | ExtensionOverrides)[]; + +// @public (undocumented) +export function convertLegacyPageExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + defaultPath?: string; + }, +): ExtensionDefinition; + +// @public (undocumented) +export function convertLegacyPlugin( + legacyPlugin: BackstagePlugin, + options: { + extensions: ExtensionDefinition[]; + }, +): FrontendPlugin; // @public export function convertLegacyRouteRef( diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 0434b206d9..a54cb8f20f 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.8-next.2", + "version": "0.3.0-next.1", "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", diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index aa3beee679..0319628b14 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -31,15 +31,15 @@ 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 { toInternalFrontendPlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; import { createPlugin, createRoutableExtension, createRouteRef, useApp, } from '@backstage/core-plugin-api'; -import { createSpecializedApp } from '@backstage/frontend-app-api'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; describe('collectLegacyRoutes', () => { it('should collect legacy routes', () => { @@ -55,7 +55,7 @@ describe('collectLegacyRoutes', () => { expect( collected.map(p => ({ id: p.id, - extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ + extensions: toInternalFrontendPlugin(p).extensions.map(e => ({ id: e.id, attachTo: e.attachTo, disabled: e.disabled, @@ -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' }, + id: 'api:score-card/plugin.scoringdata.service', + 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' }, + id: 'api:stackstorm/plugin.stackstorm.service', + 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' }, + id: 'api:puppetDb/plugin.puppetdb.service', + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -158,7 +158,7 @@ describe('collectLegacyRoutes', () => { expect( collected.map(p => ({ id: p.id, - extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ + extensions: toInternalFrontendPlugin(p).extensions.map(e => ({ id: e.id, attachTo: e.attachTo, disabled: e.disabled, @@ -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, }, { @@ -209,27 +209,27 @@ describe('collectLegacyRoutes', () => { disabled: false, }, { - id: 'api:plugin.catalog.service', + id: 'api:catalog/plugin.catalog.service', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, disabled: false, }, { - id: 'api:catalog-react.starred-entities', + id: 'api:catalog/catalog-react.starred-entities', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, disabled: false, }, { - id: 'api:plugin.catalog.entity-presentation', + id: 'api:catalog/plugin.catalog.entity-presentation', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, @@ -241,8 +241,8 @@ describe('collectLegacyRoutes', () => { id: 'score-card', extensions: [ { - id: 'api:plugin.scoringdata.service', - attachTo: { id: 'app', input: 'apis' }, + id: 'api:score-card/plugin.scoringdata.service', + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -262,7 +262,15 @@ describe('collectLegacyRoutes', () => { component: () => Promise.resolve(() => { const app = useApp(); - return
plugins: {app.getPlugins().map(p => p.getId())}
; + return ( +
+ plugins:{' '} + {app + .getPlugins() + .map(p => p.getId()) + .join(', ')} +
+ ); }), }), ); @@ -273,10 +281,10 @@ describe('collectLegacyRoutes', () => { , ); - render(createSpecializedApp({ features }).createRoot()); + renderInTestApp(
, { features }); await expect( - screen.findByText('plugins: test'), + screen.findByText('plugins: app, test'), ).resolves.toBeInTheDocument(); }); diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 3523b0a40b..43e66ce859 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,15 @@ 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({ + name: 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 4f9ba3a836..0f9ccfb2ad 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -21,8 +21,8 @@ import { AppContextProvider } from '../../../core-app-api/src/app/AppContext'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; import { - createPlugin as createNewPlugin, - BackstagePlugin as NewBackstagePlugin, + createFrontendPlugin as createNewPlugin, + FrontendPlugin as NewFrontendPlugin, appTreeApiRef, componentsApiRef, coreComponentRefs, @@ -47,11 +47,11 @@ import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; // Make sure that we only convert each new plugin instance to its legacy equivalent once const legacyPluginStore = getOrCreateGlobalSingleton( 'legacy-plugin-compatibility-store', - () => new WeakMap(), + () => new WeakMap(), ); export function toLegacyPlugin( - plugin: NewBackstagePlugin, + plugin: NewFrontendPlugin, ): LegacyBackstagePlugin { let legacy = legacyPluginStore.get(plugin); if (legacy) { @@ -83,7 +83,7 @@ export function toLegacyPlugin( } // TODO: Currently a very naive implementation, may need some more work -function toNewPlugin(plugin: LegacyBackstagePlugin): NewBackstagePlugin { +function toNewPlugin(plugin: LegacyBackstagePlugin): NewFrontendPlugin { return createNewPlugin({ id: plugin.getId(), }); diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index 04e2361087..fe4770a08c 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -42,39 +42,39 @@ import { renderInTestApp as renderInOldTestApp } from '@backstage/test-utils'; describe('BackwardsCompatProvider', () => { it('should convert the app context', () => { // TODO(Rugvip): Replace with the new renderInTestApp once it's available, and have some plugins - createExtensionTester( - createExtension({ - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory() { - function Component() { - const app = useApp(); - return ( -
- plugins: - {app - .getPlugins() - .map(p => p.getId()) - .join(', ')} - {'\n'} - components: {Object.keys(app.getComponents()).join(', ')} - {'\n'} - icons: {Object.keys(app.getSystemIcons()).join(', ')} -
- ); - } + renderInNewTestApp( + createExtensionTester( + createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + output: [coreExtensionData.reactElement], + factory() { + function Component() { + const app = useApp(); + return ( +
+ plugins:{' '} + {app + .getPlugins() + .map(p => p.getId()) + .join(', ')} + {'\n'} + components: {Object.keys(app.getComponents()).join(', ')} + {'\n'} + icons: {Object.keys(app.getSystemIcons()).join(', ')} +
+ ); + } - return { - element: compatWrapper(), - }; - }, - }), - ).render(); + return [ + coreExtensionData.reactElement(compatWrapper()), + ]; + }, + }), + ).reactElement(), + ); expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` - "plugins: + "plugins: test, app components: NotFoundErrorPage, BootErrorPage, Progress, Router, ErrorBoundaryFallback icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning" `); diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 2e7be66915..224ab46221 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' }, + id: 'api:score-card/plugin.scoringdata.service', + 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' }, + id: 'api:stackstorm/plugin.stackstorm.service', + 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' }, + id: 'api:puppetDb/plugin.puppetdb.service', + 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..3f35c78408 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -22,11 +22,13 @@ import React, { isValidElement, } from 'react'; import { - FrontendFeature, + FrontendModule, + FrontendPlugin, coreExtensionData, createExtension, + ExtensionOverrides, createExtensionInput, - createExtensionOverrides, + createFrontendModule, } from '@backstage/frontend-plugin-api'; import { getComponentData } from '@backstage/core-plugin-api'; import { collectLegacyRoutes } from './collectLegacyRoutes'; @@ -60,7 +62,7 @@ function selectChildren( /** @public */ export function convertLegacyApp( rootElement: React.JSX.Element, -): FrontendFeature[] { +): (FrontendPlugin | FrontendModule | ExtensionOverrides)[] { if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') { return collectLegacyRoutes(rootElement); } @@ -103,37 +105,32 @@ export function convertLegacyApp( const [routesEl] = routesEls; const CoreLayoutOverride = createExtension({ - namespace: 'app', 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, }); @@ -141,7 +138,8 @@ export function convertLegacyApp( return [ ...collectedRoutes, - createExtensionOverrides({ + createFrontendModule({ + pluginId: 'app', extensions: [CoreLayoutOverride, CoreNavOverride], }), ]; 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..c1630b31b5 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + 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); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('page:example'); + + 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', + }); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('page:other'); + + 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..3931a730c8 --- /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..01266a35b7 --- /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 { toInternalFrontendPlugin } 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/FrontendPlugin", + "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 = toInternalFrontendPlugin(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:test/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..5fd5e585e6 --- /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, + FrontendPlugin 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({ name: 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/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 a2463f594e..72fe6e6692 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/core-components +## 0.14.11-next.0 + +### Patch Changes + +- 06b8206: Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` +- 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 + +### 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 diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a2688a0037..ba0634bfe9 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.14.10-next.0", + "version": "0.14.11-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index 8bfb228f53..c96ab67782 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -77,7 +77,8 @@ describe('', () => { /^The above error occurred in the component:/, ), expect.stringMatching(/^ErrorBoundary/), + expect.stringMatching(/Warning: findDOMNode/), // React warning, unfortunate but currently true ]); - expect(error.length).toEqual(4); + expect(error.length).toEqual(5); }); }); diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index 98fca7c6ef..df0d256312 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -34,7 +34,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; */ export type ProxiedSignInPageProps = SignInPageProps & { /** - * The provider to use, e.g. "gcp-iap" or "awsalb". This must correspond to + * The provider to use, e.g. "gcpIap" or "awsalb". This must correspond to * a properly configured auth provider ID in the auth backend. */ provider: string; diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 95d96b701a..fb8f804d5e 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -24,7 +24,7 @@ import { UserIdentity } from './UserIdentity'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import React, { useState } from 'react'; +import React, { ReactNode, useState } from 'react'; import { useMountEffect } from '@react-hookz/web'; import { Progress } from '../../components/Progress'; import { Content } from '../Content/Content'; @@ -41,6 +41,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type MultiSignInPageProps = SignInPageProps & { providers: IdentityProviders; title?: string; + titleComponent?: ReactNode; align?: 'center' | 'left'; }; @@ -55,6 +56,7 @@ export const MultiSignInPage = ({ onSignInSuccess, providers = [], title, + titleComponent, align = 'left', }: MultiSignInPageProps) => { const configApi = useApi(configApiRef); @@ -74,7 +76,13 @@ export const MultiSignInPage = ({
- {title && } + {(title || titleComponent) && ( + + )} ({ packageVersions: { root: '1.2.3', '@backstage/cli': '1.0.0', - '@backstage/backend-common': '1.0.0', '@backstage/backend-defaults': '1.0.0', '@backstage/backend-tasks': '1.0.0', '@backstage/catalog-model': '1.0.0', diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 194ffa21d9..84fbc25450 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -32,9 +32,7 @@ leaving any imports in place. import { version as root } from '../../../../package.json'; import { version as appDefaults } from '../../../app-defaults/package.json'; -import { version as backendCommon } from '../../../backend-common/package.json'; import { version as backendDefaults } from '../../../backend-defaults/package.json'; -import { version as backendTasks } from '../../../backend-tasks/package.json'; import { version as catalogClient } from '../../../catalog-client/package.json'; import { version as catalogModel } from '../../../catalog-model/package.json'; import { version as cli } from '../../../cli/package.json'; @@ -89,9 +87,7 @@ import { version as pluginUserSettings } from '../../../../plugins/user-settings export const packageVersions = { root, '@backstage/app-defaults': appDefaults, - '@backstage/backend-common': backendCommon, '@backstage/backend-defaults': backendDefaults, - '@backstage/backend-tasks': backendTasks, '@backstage/catalog-client': catalogClient, '@backstage/catalog-model': catalogModel, '@backstage/cli': cli, diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index bef67b5ce7..22fe721379 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -34,7 +34,7 @@ USER node WORKDIR /app # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 757d849fd7..c7625ba5cc 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -16,9 +16,7 @@ "build-image": "docker build ../.. -f Dockerfile --tag backstage" }, "dependencies": { - "@backstage/backend-common": "^{{version '@backstage/backend-common'}}", "@backstage/backend-defaults": "^{{version '@backstage/backend-defaults'}}", - "@backstage/backend-tasks": "^{{version '@backstage/backend-tasks'}}", "@backstage/config": "^{{version '@backstage/config'}}", "@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}", "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", diff --git a/packages/create-app/templates/default-app/tsconfig.json b/packages/create-app/templates/default-app/tsconfig.json index ba3f90177d..2aa745d3a9 100644 --- a/packages/create-app/templates/default-app/tsconfig.json +++ b/packages/create-app/templates/default-app/tsconfig.json @@ -2,7 +2,9 @@ "extends": "@backstage/cli/config/tsconfig.json", "include": [ "packages/*/src", + "packages/*/config.d.ts", "plugins/*/src", + "plugins/*/config.d.ts", "plugins/*/dev", "plugins/*/migrations" ], diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 5ea08c7762..e71ef37738 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/dev-utils +## 1.0.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## 1.0.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## 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 + +## 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 + ## 1.0.37-next.2 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index b2b6eb3292..71a4de45ca 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.37-next.2", + "version": "1.0.38-next.1", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 4609771fb5..f553c6e450 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,41 @@ # e2e-test +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## 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 + ## 0.2.19-next.3 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 481b011d01..db0b649123 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.19-next.3", + "version": "0.2.20-next.1", "private": true, "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index dd419c284b..9596e51ee3 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/frontend-app-api +## 0.9.0-next.1 + +### Minor Changes + +- 7c80650: **BREAKING**: The `createSpecializedApp` function now creates a bare-bones app without any of the default app structure or APIs. To re-introduce this functionality if you need to use `createSpecializedApp` you can install the `app` plugin from `@backstage/plugin-app`. + + In addition, the `createApp` and `CreateAppFeatureLoader` exports are now deprecated as they are being moved to `@backstage/frontend-defaults`, which should be used instead. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 0.9.0-next.0 + +### Minor Changes + +- 62cce6c: Removed deprecated `icons` property passing to `createApp` and `createSpecializedApp`. Use `IconBundleBlueprint.make` to create extensions instead and include them in the app. + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. +- f3a2b91: Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension. +- 5446061: Internal refactor following removal of v1 extension support. The app implementation itself still supports v1 extensions at runtime. +- 98850de: Added support for defining `replaces` in `createExtensionInput` which will allow extensions to redirect missing `attachTo` points to an input of the created extension. + + ```ts + export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + // attachTo: { id: 'app', input: 'themes'} will be redirected to this input instead + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: () { + ... + } + }); + ``` + +- 4a66456: Added the `root` extension the replace the `app` extension as the root of the app. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @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/version-bridge@1.0.8 + +## 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 + +## 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 + ## 0.7.5-next.2 ### Patch Changes diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md index 27d486b6a8..e102d6bf20 100644 --- a/packages/frontend-app-api/api-report.md +++ b/packages/frontend-app-api/api-report.md @@ -4,36 +4,20 @@ ```ts import { ConfigApi } from '@backstage/core-plugin-api'; +import { createApp as createApp_2 } from '@backstage/frontend-defaults'; +import { CreateAppFeatureLoader as CreateAppFeatureLoader_2 } from '@backstage/frontend-defaults'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { FrontendModule } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; -import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; -// @public (undocumented) -export function createApp(options?: { - icons?: { - [key in string]: IconComponent; - }; - features?: (FrontendFeature | CreateAppFeatureLoader)[]; - configLoader?: () => Promise<{ - config: ConfigApi; - }>; - bindRoutes?(context: { bind: CreateAppRouteBinder }): void; - loadingComponent?: ReactNode; -}): { - createRoot(): JSX_2.Element; -}; +// @public @deprecated (undocumented) +export const createApp: typeof createApp_2; -// @public -export interface CreateAppFeatureLoader { - getLoaderName(): string; - load(options: { config: ConfigApi }): Promise<{ - features: FrontendFeature[]; - }>; -} +// @public @deprecated (undocumented) +export type CreateAppFeatureLoader = CreateAppFeatureLoader_2; // @public export type CreateAppRouteBinder = < @@ -50,13 +34,21 @@ export type CreateAppRouteBinder = < // @public export function createSpecializedApp(options?: { - icons?: { - [key in string]: IconComponent; - }; features?: FrontendFeature[]; config?: ConfigApi; bindRoutes?(context: { bind: CreateAppRouteBinder }): void; }): { createRoot(): JSX_2.Element; }; + +// @public (undocumented) +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + | { + $$type: '@backstage/ExtensionOverrides'; + } + | { + $$type: '@backstage/BackstagePlugin'; + }; ``` diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 0d462e2721..140266eae6 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.7.5-next.2", + "version": "0.9.0-next.1", "backstage": { "role": "web-library" }, @@ -34,20 +34,18 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-defaults": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "@material-ui/core": "^4.12.4", - "@material-ui/icons": "^4.11.3", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", "lodash": "^4.17.21" }, "devDependencies": { "@backstage/cli": "workspace:^", + "@backstage/plugin-app": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0" diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx index 7d62103727..3d19fc413f 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx @@ -15,63 +15,22 @@ */ import React from 'react'; -import { - coreExtensionData, - createComponentExtension, - createComponentRef, - createExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; -import { resolveAppNodeSpecs } from '../../../tree/resolveAppNodeSpecs'; -import { resolveAppTree } from '../../../tree/resolveAppTree'; -import { App } from '../../../extensions/App'; +import { createComponentRef } from '@backstage/frontend-plugin-api'; import { DefaultComponentsApi } from './DefaultComponentsApi'; import { render, screen } from '@testing-library/react'; -import { instantiateAppNodeTree } from '../../../tree/instantiateAppNodeTree'; const testRefA = createComponentRef({ id: 'test.a' }); const testRefB1 = createComponentRef({ id: 'test.b' }); const testRefB2 = createComponentRef({ id: 'test.b' }); -const baseOverrides = createExtensionOverrides({ - extensions: [ - App, - createExtension({ - namespace: 'app', - name: 'root', - attachTo: { id: 'app', input: 'root' }, - output: { - element: coreExtensionData.reactElement, - }, - factory() { - return { - element:
root
, - }; - }, - }), - ], -}); - describe('DefaultComponentsApi', () => { it('should provide components', () => { - const tree = resolveAppTree( - 'app', - resolveAppNodeSpecs({ - features: [ - baseOverrides, - createExtensionOverrides({ - extensions: [ - createComponentExtension({ - ref: testRefA, - loader: { sync: () => () =>
test.a
}, - }), - ], - }), - ], - }), - ); - instantiateAppNodeTree(tree.root); - const api = DefaultComponentsApi.fromTree(tree); + const api = DefaultComponentsApi.fromComponents([ + { + ref: testRefA, + impl: () =>
test.a
, + }, + ]); const ComponentA = api.getComponent(testRefA); render(); @@ -80,24 +39,12 @@ describe('DefaultComponentsApi', () => { }); it('should key extension refs by ID', () => { - const tree = resolveAppTree( - 'app', - resolveAppNodeSpecs({ - features: [ - baseOverrides, - createExtensionOverrides({ - extensions: [ - createComponentExtension({ - ref: testRefB1, - loader: { sync: () => () =>
test.b
}, - }), - ], - }), - ], - }), - ); - instantiateAppNodeTree(tree.root); - const api = DefaultComponentsApi.fromTree(tree); + const api = DefaultComponentsApi.fromComponents([ + { + ref: testRefB1, + impl: () =>
test.b
, + }, + ]); const ComponentB1 = api.getComponent(testRefB1); const ComponentB2 = api.getComponent(testRefB2); diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts index 572b6fc3c4..317233d797 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts @@ -16,7 +16,6 @@ import { ComponentType } from 'react'; import { - AppTree, ComponentRef, ComponentsApi, createComponentExtension, @@ -30,19 +29,12 @@ import { export class DefaultComponentsApi implements ComponentsApi { #components: Map>; - static fromTree(tree: AppTree) { - const componentEntries = tree.root.edges.attachments - .get('components') - ?.reduce((map, e) => { - const data = e.instance?.getData( - createComponentExtension.componentDataRef, - ); - if (data) { - map.set(data.ref.id, data.impl); - } - return map; - }, new Map()); - return new DefaultComponentsApi(componentEntries ?? new Map()); + static fromComponents( + components: Array, + ) { + return new DefaultComponentsApi( + new Map(components.map(entry => [entry.ref.id, entry.impl])), + ); } constructor(components: Map) { diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx deleted file mode 100644 index 7794aceb79..0000000000 --- a/packages/frontend-app-api/src/extensions/App.tsx +++ /dev/null @@ -1,68 +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 from 'react'; -import { - ExtensionBoundary, - coreExtensionData, - createApiExtension, - createComponentExtension, - createExtension, - createExtensionInput, - createThemeExtension, - createTranslationExtension, - IconBundleBlueprint, -} from '@backstage/frontend-plugin-api'; - -export const App = createExtension({ - namespace: 'app', - attachTo: { id: 'root', input: 'default' }, // ignored - inputs: { - apis: createExtensionInput({ - api: createApiExtension.factoryDataRef, - }), - themes: createExtensionInput({ - theme: createThemeExtension.themeDataRef, - }), - components: createExtensionInput({ - component: createComponentExtension.componentDataRef, - }), - translations: createExtensionInput({ - translation: createTranslationExtension.translationDataRef, - }), - icons: createExtensionInput({ - icon: IconBundleBlueprint.dataRefs.icons, - }), - root: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, - ), - }, - output: { - root: coreExtensionData.reactElement, - }, - factory({ node, inputs }) { - return { - root: ( - - {inputs.root.output.element} - - ), - }; - }, -}); diff --git a/packages/frontend-app-api/src/extensions/Root.ts b/packages/frontend-app-api/src/extensions/Root.ts new file mode 100644 index 0000000000..66c4db3140 --- /dev/null +++ b/packages/frontend-app-api/src/extensions/Root.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 { + ApiBlueprint, + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; + +export const Root = createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + app: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + apis: createExtensionInput([ApiBlueprint.dataRefs.factory], { + replaces: [{ id: 'app', input: 'apis' }], + }), + }, + output: [coreExtensionData.reactElement], + factory: ({ inputs }) => inputs.app, +}); diff --git a/packages/frontend-app-api/src/extensions/elements.tsx b/packages/frontend-app-api/src/extensions/elements.tsx deleted file mode 100644 index 0a764583bb..0000000000 --- a/packages/frontend-app-api/src/extensions/elements.tsx +++ /dev/null @@ -1,45 +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 { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; -import { - createAppRootElementExtension, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import React from 'react'; - -export const oauthRequestDialogAppRootElement = createAppRootElementExtension({ - namespace: 'app', - name: 'oauth-request-dialog', - element: , -}); - -export const alertDisplayAppRootElement = createAppRootElementExtension({ - namespace: 'app', - name: 'alert-display', - configSchema: createSchemaFromZod(z => - z.object({ - transientTimeoutMs: z.number().default(5000), - anchorOrigin: z - .object({ - vertical: z.enum(['top', 'bottom']).default('top'), - horizontal: z.enum(['left', 'center', 'right']).default('center'), - }) - .default({}), - }), - ), - element: ({ config }) => , -}); diff --git a/packages/frontend-app-api/src/extensions/themes.tsx b/packages/frontend-app-api/src/extensions/themes.tsx deleted file mode 100644 index ea11b6a638..0000000000 --- a/packages/frontend-app-api/src/extensions/themes.tsx +++ /dev/null @@ -1,44 +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 from 'react'; -import { - UnifiedThemeProvider, - themes as builtinThemes, -} from '@backstage/theme'; -import DarkIcon from '@material-ui/icons/Brightness2'; -import LightIcon from '@material-ui/icons/WbSunny'; -import { createThemeExtension } from '@backstage/frontend-plugin-api'; - -export const LightTheme = createThemeExtension({ - id: 'light', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => ( - - ), -}); - -export const DarkTheme = createThemeExtension({ - id: 'dark', - title: 'Dark Theme', - variant: 'dark', - icon: , - Provider: ({ children }) => ( - - ), -}); diff --git a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx index 935449b697..aeec03992f 100644 --- a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx +++ b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx @@ -46,7 +46,10 @@ describe('RouteTracker', () => { caseSensitive: false, children: [MATCH_ALL_ROUTE], appNode: { - spec: { extension: { id: 'home.page.index' }, source: { id: 'home' } }, + spec: { + extension: { id: 'home.page.index' }, + source: { id: 'home' }, + }, } as AppNode, }, { diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts index 6b9fdecf70..d74ab43e33 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts @@ -17,7 +17,7 @@ import { createRouteRef, createExternalRouteRef, - createPlugin, + createFrontendPlugin, } from '@backstage/frontend-plugin-api'; import { collectRouteIds } from './collectRouteIds'; @@ -34,7 +34,11 @@ describe('collectRouteIds', () => { ); const collected = collectRouteIds([ - createPlugin({ id: 'test', routes: { ref }, externalRoutes: { extRef } }), + createFrontendPlugin({ + id: 'test', + routes: { ref }, + externalRoutes: { extRef }, + }), ]); expect(Object.fromEntries(collected.routes)).toEqual({ 'test.ref': ref, diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index d45871ae89..d89834c02e 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -18,7 +18,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef, - FrontendFeature, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { @@ -29,6 +28,9 @@ import { import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { isInternalFrontendPlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +import { FrontendFeature } from '../wiring'; /** @internal */ export interface RouteRefsById { @@ -42,7 +44,7 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { const externalRoutesById = new Map(); for (const feature of features) { - if (feature.$$type !== '@backstage/BackstagePlugin') { + if (!isInternalFrontendPlugin(feature)) { continue; } diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index bdab2781e0..7ffe64d6d7 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -25,12 +25,19 @@ import { coreExtensionData, createExtension, createExtensionInput, - createPlugin, + createFrontendPlugin, createRouteRef, } from '@backstage/frontend-plugin-api'; -import { MockConfigApi } from '@backstage/test-utils'; -import { createAppTree } from '../tree'; -import { builtinExtensions } from '../wiring/createApp'; +import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils'; +import appPlugin from '@backstage/plugin-app'; + +import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig'; +import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs'; +import { resolveAppTree } from '../tree/resolveAppTree'; +import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree'; +import { Root } from '../extensions/Root'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; const ref1 = createRouteRef(); const ref2 = createRouteRef(); @@ -50,36 +57,47 @@ function createTestExtension(options: { attachTo: options.parent ? { id: `test/${options.parent}`, input: 'children' } : { id: 'app/routes', input: 'routes' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ], inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + children: createExtensionInput([coreExtensionData.reactElement]), }, - factory() { - return { - path: options.path, - routeRef: options.routeRef, - element: React.createElement('div'), - }; + *factory() { + if (options.path !== undefined) { + yield coreExtensionData.routePath(options.path); + } + + if (options.routeRef) { + yield coreExtensionData.routeRef(options.routeRef); + } + + yield coreExtensionData.reactElement(React.createElement('div')); }, }); } -function routeInfoFromExtensions(extensions: ExtensionDefinition[]) { - const plugin = createPlugin({ +function routeInfoFromExtensions(extensions: ExtensionDefinition[]) { + const plugin = createFrontendPlugin({ id: 'test', extensions, }); - const tree = createAppTree({ - config: new MockConfigApi({}), - builtinExtensions, - features: [plugin], - }); + + const tree = resolveAppTree( + 'root', + resolveAppNodeSpecs({ + features: [appPlugin, plugin], + builtinExtensions: [ + resolveExtensionDefinition(Root, { namespace: 'root' }), + ], + parameters: readAppExtensionsConfig(new MockConfigApi({})), + forbidden: new Set(['root']), + }), + ); + + instantiateAppNodeTree(tree.root, TestApiRegistry.from()); return extractRouteInfoFromAppNode(tree.root); } diff --git a/packages/frontend-app-api/src/tree/createAppTree.test.ts b/packages/frontend-app-api/src/tree/createAppTree.test.ts deleted file mode 100644 index 9ee58fe2d2..0000000000 --- a/packages/frontend-app-api/src/tree/createAppTree.test.ts +++ /dev/null @@ -1,96 +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 { - createExtension, - createExtensionOverrides, - createPlugin, -} from '@backstage/frontend-plugin-api'; -import { MockConfigApi } from '@backstage/test-utils'; -import { createAppTree } from './createAppTree'; - -const extBase = { - id: 'test', - attachTo: { id: 'app', input: 'root' }, - output: {}, - factory: () => ({}), -}; - -describe('createAppTree', () => { - it('throws an error when a app extension is parametrized', () => { - const config = new MockConfigApi({ - app: { - extensions: [ - { - app: {}, - }, - ], - }, - }); - const features = [ - createPlugin({ - id: 'plugin', - extensions: [], - }), - ]; - expect(() => - createAppTree({ features, config, builtinExtensions: [] }), - ).toThrow("Configuration of the 'app' extension is forbidden"); - }); - - it('throws an error when a app extension is overridden', () => { - const config = new MockConfigApi({}); - const features = [ - createExtensionOverrides({ - extensions: [ - createExtension({ - name: 'app', - attachTo: { id: 'app/routes', input: 'route' }, - inputs: {}, - output: {}, - factory: () => ({}), - }), - ], - }), - ]; - expect(() => - createAppTree({ features, config, builtinExtensions: [] }), - ).toThrow( - "It is forbidden to override the following extension(s): 'app', which is done by one or more extension overrides", - ); - }); - - it('throws an error when duplicated extension overrides are detected', () => { - expect(() => - createAppTree({ - features: [ - createExtensionOverrides({ - extensions: [ - createExtension({ ...extBase, name: 'a' }), - createExtension({ ...extBase, name: 'a' }), - createExtension({ ...extBase, name: 'b' }), - ], - }), - createExtensionOverrides({ - extensions: [createExtension({ ...extBase, name: 'b' })], - }), - ], - config: new MockConfigApi({}), - builtinExtensions: [], - }), - ).toThrow('The following extensions had duplicate overrides: a, b'); - }); -}); diff --git a/packages/frontend-app-api/src/tree/createAppTree.ts b/packages/frontend-app-api/src/tree/createAppTree.ts deleted file mode 100644 index ee9704c3c3..0000000000 --- a/packages/frontend-app-api/src/tree/createAppTree.ts +++ /dev/null @@ -1,45 +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 { Extension, FrontendFeature } from '@backstage/frontend-plugin-api'; -import { readAppExtensionsConfig } from './readAppExtensionsConfig'; -import { resolveAppTree } from './resolveAppTree'; -import { resolveAppNodeSpecs } from './resolveAppNodeSpecs'; -import { AppTree } from '@backstage/frontend-plugin-api'; -import { Config } from '@backstage/config'; -import { instantiateAppNodeTree } from './instantiateAppNodeTree'; - -/** @internal */ -export interface CreateAppTreeOptions { - features: FrontendFeature[]; - builtinExtensions: Extension[]; - config: Config; -} - -/** @internal */ -export function createAppTree(options: CreateAppTreeOptions): AppTree { - const tree = resolveAppTree( - 'app', - resolveAppNodeSpecs({ - features: options.features, - builtinExtensions: options.builtinExtensions, - parameters: readAppExtensionsConfig(options.config), - forbidden: new Set(['app']), - }), - ); - instantiateAppNodeTree(tree.root); - return tree; -} diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index aded5d7826..f24a9e17f3 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -15,14 +15,15 @@ */ import { + AnyExtensionDataRef, AppNode, Extension, ExtensionInput, + PortableSchema, ResolvedExtensionInput, createExtension, createExtensionDataRef, createExtensionInput, - createSchemaFromZod, } from '@backstage/frontend-plugin-api'; import { createAppNodeInstance, @@ -31,9 +32,15 @@ import { import { AppNodeSpec } from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; -import { withLogCollector } from '@backstage/test-utils'; +import { + InternalExtension, + resolveExtensionDefinition, +} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { createSchemaFromZod } from '../../../frontend-plugin-api/src/schema/createSchemaFromZod'; +import { TestApiRegistry, withLogCollector } from '@backstage/test-utils'; +const testApis = TestApiRegistry.from(); const testDataRef = createExtensionDataRef().with({ id: 'test' }); const otherDataRef = createExtensionDataRef().with({ id: 'other' }); const inputMirrorDataRef = createExtensionDataRef().with({ @@ -76,44 +83,80 @@ function makeInstanceWithId( instance: createAppNodeInstance({ node, attachments: new Map(), + apis: testApis, }), }; } +function createV1ExtensionInput( + extensionData: Record, + options: { singleton?: boolean; optional?: boolean } = {}, +) { + return { + $$type: '@backstage/ExtensionInput' as const, + extensionData, + config: { + singleton: options.singleton ?? false, + optional: options.optional ?? false, + }, + }; +} + +function createV1Extension(opts: { + namespace: string; + name?: string; + attachTo?: { id: string; input: string }; + inputs?: Record>; + output: Record; + configSchema?: PortableSchema; + factory: (ctx: { inputs: any; config: any }) => any; +}): Extension { + const ext: InternalExtension = { + $$type: '@backstage/Extension', + version: 'v1', + id: opts.name ? `${opts.namespace}/${opts.name}` : opts.namespace, + disabled: false, + attachTo: opts.attachTo ?? { id: 'ignored', input: 'ignored' }, + inputs: opts.inputs ?? {}, + output: opts.output, + configSchema: opts.configSchema, + factory: opts.factory, + }; + return ext; +} + describe('instantiateAppNodeTree', () => { describe('v1', () => { - const simpleExtension = resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - other: otherDataRef.optional(), - }, - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), - factory({ config }) { - return { test: config.output, other: config.other }; - }, - }), - ); + const simpleExtension = createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + other: otherDataRef.optional(), + }, + configSchema: createSchemaFromZod(z => + z.object({ + output: z.string().default('test'), + other: z.number().optional(), + }), + ), + factory({ config }) { + return { test: config.output, other: config.other }; + }, + }); it('should instantiate a single node', () => { const tree = resolveAppTree('root-node', [ makeSpec(simpleExtension, { id: 'root-node' }), ]); expect(tree.root.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(tree.root.instance?.getData(testDataRef)).toBe('test'); // Multiple calls should have no effect - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); }); @@ -122,28 +165,26 @@ describe('instantiateAppNodeTree', () => { makeSpec(simpleExtension, { id: 'root-node', disabled: true }), ]); expect(tree.root.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).not.toBeDefined(); }); it('should instantiate a node with attachments', () => { const tree = resolveAppTree('root-node', [ makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), makeSpec(simpleExtension, { id: 'child-node', @@ -156,7 +197,7 @@ describe('instantiateAppNodeTree', () => { expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({ @@ -166,7 +207,7 @@ describe('instantiateAppNodeTree', () => { }); // Multiple calls should have no effect - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); }); @@ -175,21 +216,19 @@ describe('instantiateAppNodeTree', () => { const tree = resolveAppTree('root-node', [ { ...makeSpec( - resolveExtensionDefinition( - createExtension({ - namespace: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + createV1Extension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createV1ExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }, { @@ -206,7 +245,7 @@ describe('instantiateAppNodeTree', () => { expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).not.toBeDefined(); expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({ @@ -220,6 +259,7 @@ describe('instantiateAppNodeTree', () => { const instance = createAppNodeInstance({ node: makeNode(simpleExtension), attachments, + apis: testApis, }); expect(Array.from(instance.getDataRefs())).toEqual([ @@ -260,48 +300,47 @@ describe('instantiateAppNodeTree', () => { ], ]); const instance = createAppNodeInstance({ + apis: testApis, attachments, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - optionalSingletonPresent: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - optionalSingletonMissing: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - singleton: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true }, - ), - many: createExtensionInput({ + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + optionalSingletonPresent: createV1ExtensionInput( + { test: testDataRef, other: otherDataRef.optional(), - }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), - ), + }, + { singleton: true, optional: true }, + ), + optionalSingletonMissing: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true, optional: true }, + ), + singleton: createV1ExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true }, + ), + many: createV1ExtensionInput({ + test: testDataRef, + other: otherDataRef.optional(), + }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), ), }); @@ -330,6 +369,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an extension with invalid config', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode(simpleExtension, { config: { other: 'not-a-number' }, }), @@ -343,20 +383,19 @@ describe('instantiateAppNodeTree', () => { it('should forward extension factory errors', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory() { - const error = new Error('NOPE'); - error.name = 'NopeError'; - throw error; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory() { + const error = new Error('NOPE'); + error.name = 'NopeError'; + throw error; + }, + }), ), attachments: new Map(), }), @@ -368,21 +407,20 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with duplicate output', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test1: testDataRef, - test2: testDataRef, - }, - factory({}) { - return { test1: 'test', test2: 'test2' }; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test1: testDataRef, + test2: testDataRef, + }, + factory({}) { + return { test1: 'test', test2: 'test2' }; + }, + }), ), attachments: new Map(), }), @@ -394,20 +432,19 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with disconnected output data', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - }, - factory({}) { - return { nonexistent: 'test' } as any; - }, - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + }, + factory({}) { + return { nonexistent: 'test' } as any; + }, + }), ), attachments: new Map(), }), @@ -419,24 +456,23 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with missing required input', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), attachments: new Map(), }), @@ -448,6 +484,7 @@ describe('instantiateAppNodeTree', () => { it('should warn when creating an instance with undeclared inputs', () => { const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'declared', @@ -467,20 +504,18 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - declared: createExtensionInput({ - test: testDataRef, - }), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + declared: createV1ExtensionInput({ + test: testDataRef, + }), + }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -493,6 +528,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple undeclared inputs', () => { const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'undeclared1', @@ -507,15 +543,13 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'parent', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory: () => ({}), + }), ), }), ); @@ -529,6 +563,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs for required singleton', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'singleton', @@ -539,23 +574,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -566,6 +599,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs for optional singleton', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'singleton', @@ -576,23 +610,21 @@ describe('instantiateAppNodeTree', () => { ], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true, optional: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + test: testDataRef, + }, + { singleton: true, optional: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrow( @@ -603,27 +635,26 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs that did not provide required data', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], ]), node: makeNode( - resolveExtensionDefinition( - createExtension({ - namespace: 'app', - name: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - other: otherDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), - ), + createV1Extension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createV1ExtensionInput( + { + other: otherDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), ), }), ).toThrowErrorMatchingInlineSnapshot( @@ -636,7 +667,6 @@ describe('instantiateAppNodeTree', () => { describe('v2', () => { const simpleExtension = resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef, otherDataRef.optional()], @@ -653,6 +683,7 @@ describe('instantiateAppNodeTree', () => { ]; }, }), + { namespace: 'app' }, ); function mirrorInputs(ctx: { @@ -696,12 +727,12 @@ describe('instantiateAppNodeTree', () => { makeSpec(simpleExtension, { id: 'root-node' }), ]); expect(tree.root.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(tree.root.instance?.getData(testDataRef)).toBe('test'); // Multiple calls should have no effect - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); }); @@ -710,7 +741,7 @@ describe('instantiateAppNodeTree', () => { makeSpec(simpleExtension, { id: 'root-node', disabled: true }), ]); expect(tree.root.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).not.toBeDefined(); }); @@ -719,7 +750,6 @@ describe('instantiateAppNodeTree', () => { makeSpec( resolveExtensionDefinition( createExtension({ - namespace: 'root-node', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { test: createExtensionInput([testDataRef]), @@ -727,6 +757,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'root-node' }, ), ), makeSpec(simpleExtension, { @@ -740,7 +771,7 @@ describe('instantiateAppNodeTree', () => { expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({ @@ -748,7 +779,7 @@ describe('instantiateAppNodeTree', () => { }); // Multiple calls should have no effect - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); }); @@ -759,7 +790,6 @@ describe('instantiateAppNodeTree', () => { ...makeSpec( resolveExtensionDefinition( createExtension({ - namespace: 'root-node', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { test: createExtensionInput([testDataRef]), @@ -767,6 +797,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'root-node' }, ), ), }, @@ -784,7 +815,7 @@ describe('instantiateAppNodeTree', () => { expect(tree.root.instance).not.toBeDefined(); expect(childNode?.instance).not.toBeDefined(); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, testApis); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).not.toBeDefined(); expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({ @@ -798,6 +829,7 @@ describe('instantiateAppNodeTree', () => { const instance = createAppNodeInstance({ node: makeNode(simpleExtension), attachments, + apis: testApis, }); expect(Array.from(instance.getDataRefs())).toEqual([testDataRef]); @@ -837,10 +869,10 @@ describe('instantiateAppNodeTree', () => { const instance = createAppNodeInstance({ attachments, + apis: testApis, node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -864,6 +896,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'app' }, ), ), }); @@ -895,6 +928,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an extension with invalid config', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode(simpleExtension, { config: { other: 'not-a-number' }, }), @@ -908,10 +942,10 @@ describe('instantiateAppNodeTree', () => { it('should forward extension factory errors', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef], @@ -921,6 +955,7 @@ describe('instantiateAppNodeTree', () => { throw error; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -933,10 +968,10 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with duplicate output', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef, testDataRef], @@ -944,6 +979,7 @@ describe('instantiateAppNodeTree', () => { return [testDataRef('test'), testDataRef('test2')]; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -956,10 +992,10 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance without required', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef], @@ -967,6 +1003,7 @@ describe('instantiateAppNodeTree', () => { return [] as any; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -979,11 +1016,11 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with unknown output data', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( resolveExtensionDefinition( // @ts-expect-error createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [], // Output not declared @@ -991,6 +1028,7 @@ describe('instantiateAppNodeTree', () => { return [testDataRef('test')] as any; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -1003,10 +1041,10 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with missing required input', () => { expect(() => createAppNodeInstance({ + apis: testApis, node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1017,6 +1055,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -1029,6 +1068,7 @@ describe('instantiateAppNodeTree', () => { it('should warn when creating an instance with undeclared inputs', () => { const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'declared', @@ -1050,7 +1090,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'parent', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1059,6 +1098,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1072,6 +1112,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple undeclared inputs', () => { const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'undeclared1', @@ -1088,12 +1129,12 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'parent', attachTo: { id: 'ignored', input: 'ignored' }, output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1108,6 +1149,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs for required singleton', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'singleton', @@ -1120,7 +1162,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1131,6 +1172,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1142,6 +1184,7 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs for optional singleton', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ [ 'singleton', @@ -1154,7 +1197,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1166,6 +1208,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1177,13 +1220,13 @@ describe('instantiateAppNodeTree', () => { it('should refuse to create an instance with multiple inputs that did not provide required data', () => { expect(() => createAppNodeInstance({ + apis: testApis, attachments: new Map([ ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], ]), node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1194,6 +1237,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 65626e0be1..cc17bfae27 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -15,9 +15,8 @@ */ import { - AnyExtensionDataMap, AnyExtensionDataRef, - AnyExtensionInputMap, + ApiHolder, ExtensionDataContainer, ExtensionDataRef, ExtensionInput, @@ -32,8 +31,10 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; -function resolveInputDataMap( - dataMap: AnyExtensionDataMap, +function resolveV1InputDataMap( + dataMap: { + [name in string]: AnyExtensionDataRef; + }, attachment: AppNode, inputName: string, ) { @@ -92,6 +93,16 @@ function resolveInputDataContainer( get(ref) { return dataMap.get(ref.id); }, + *[Symbol.iterator]() { + for (const [id, value] of dataMap) { + // TODO: Would be better to be able to create a new instance using the ref here instead + yield { + $$type: '@backstage/ExtensionDataValue', + id, + value, + }; + } + }, } as { node: AppNode } & ExtensionDataContainer; } @@ -124,9 +135,17 @@ function reportUndeclaredAttachments( } function resolveV1Inputs( - inputMap: AnyExtensionInputMap, + inputMap: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }, attachments: ReadonlyMap, -): ResolvedExtensionInputs { +) { return mapValues(inputMap, (input, inputName) => { const attachedNodes = attachments.get(inputName) ?? []; @@ -148,7 +167,7 @@ function resolveV1Inputs( } return { node: attachedNodes[0], - output: resolveInputDataMap( + output: resolveV1InputDataMap( input.extensionData, attachedNodes[0], inputName, @@ -158,9 +177,16 @@ function resolveV1Inputs( return attachedNodes.map(attachment => ({ node: attachment, - output: resolveInputDataMap(input.extensionData, attachment, inputName), + output: resolveV1InputDataMap(input.extensionData, attachment, inputName), })); - }) as ResolvedExtensionInputs; + }) as { + [inputName in string]: { + node: AppNode; + output: { + [name in string]: unknown; + }; + }; + }; } function resolveV2Inputs( @@ -217,9 +243,10 @@ function resolveV2Inputs( /** @internal */ export function createAppNodeInstance(options: { node: AppNode; + apis: ApiHolder; attachments: ReadonlyMap; }): AppNodeInstance { - const { node, attachments } = options; + const { node, apis, attachments } = options; const { id, extension, config } = node.spec; const extensionData = new Map(); const extensionDataRefs = new Set>(); @@ -243,6 +270,7 @@ export function createAppNodeInstance(options: { if (internalExtension.version === 'v1') { const namedOutputs = internalExtension.factory({ node, + apis, config: parsedConfig, inputs: resolveV1Inputs(internalExtension.inputs, attachments), }); @@ -263,6 +291,7 @@ export function createAppNodeInstance(options: { } else if (internalExtension.version === 'v2') { const outputDataValues = internalExtension.factory({ node, + apis, config: parsedConfig, inputs: resolveV2Inputs(internalExtension.inputs, attachments), }); @@ -324,7 +353,10 @@ export function createAppNodeInstance(options: { * Starting at the provided node, instantiate all reachable nodes in the tree that have not been disabled. * @internal */ -export function instantiateAppNodeTree(rootNode: AppNode): void { +export function instantiateAppNodeTree( + rootNode: AppNode, + apis: ApiHolder, +): void { function createInstance(node: AppNode): AppNodeInstance | undefined { if (node.instance) { return node.instance; @@ -350,6 +382,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void { (node as Mutable).instance = createAppNodeInstance({ node, + apis, attachments: instantiatedAttachments, }); diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 65f3f77640..e4500df6c0 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -16,7 +16,8 @@ import { createExtensionOverrides, - createPlugin, + createFrontendModule, + createFrontendPlugin, Extension, ExtensionDefinition, } from '@backstage/frontend-plugin-api'; @@ -38,18 +39,19 @@ function makeExt( } function makeExtDef( - name: string, + name: string | undefined = undefined, status: 'disabled' | 'enabled' = 'enabled', attachId: string = 'root', ) { return { $$type: '@backstage/ExtensionDefinition', + T: undefined as any, version: 'v1', name, attachTo: { id: attachId, input: 'default' }, disabled: status === 'disabled', - override: () => ({} as ExtensionDefinition), - } as ExtensionDefinition; + override: () => ({} as ExtensionDefinition), + } as ExtensionDefinition; } describe('resolveAppNodeSpecs', () => { @@ -98,7 +100,10 @@ describe('resolveAppNodeSpecs', () => { it('should override attachment points', () => { const b = makeExt('b'); - const pluginA = createPlugin({ id: 'test', extensions: [makeExtDef('a')] }); + const pluginA = createFrontendPlugin({ + id: 'test', + extensions: [makeExtDef('a')], + }); expect( resolveAppNodeSpecs({ features: [pluginA], @@ -130,7 +135,7 @@ describe('resolveAppNodeSpecs', () => { it('should fully override configuration and duplicate', () => { const a = makeExt('test/a'); const b = makeExt('test/b'); - const plugin = createPlugin({ + const plugin = createFrontendPlugin({ id: 'test', extensions: [makeExtDef('a'), makeExtDef('b')], }); @@ -178,7 +183,7 @@ describe('resolveAppNodeSpecs', () => { const b = makeExt('b', 'disabled'); expect( resolveAppNodeSpecs({ - features: [createPlugin({ id: 'empty', extensions: [] })], + features: [createFrontendPlugin({ id: 'empty', extensions: [] })], builtinExtensions: [a, b], parameters: [ { @@ -217,7 +222,7 @@ describe('resolveAppNodeSpecs', () => { const g = makeExt('g', 'disabled'); expect( resolveAppNodeSpecs({ - features: [createPlugin({ id: 'empty', extensions: [] })], + features: [createFrontendPlugin({ id: 'empty', extensions: [] })], builtinExtensions: [a, b, c, d, e, f, g], parameters: [ { id: 'e', disabled: false }, @@ -272,7 +277,7 @@ describe('resolveAppNodeSpecs', () => { }); it('should apply extension overrides', () => { - const plugin = createPlugin({ + const plugin = createFrontendPlugin({ id: 'test', extensions: [makeExtDef('a'), makeExtDef('b')], }); @@ -320,10 +325,60 @@ describe('resolveAppNodeSpecs', () => { ]); }); + it('should apply module overrides', () => { + const plugin = createFrontendPlugin({ + id: 'test', + extensions: [makeExtDef('a'), makeExtDef('b')], + }); + const aOverride = makeExt('test/a', 'enabled', 'other'); + const bOverride = makeExt('test/b', 'disabled', 'other'); + const cOverride = makeExt('test/c'); + + expect( + resolveAppNodeSpecs({ + features: [ + plugin, + createFrontendModule({ + pluginId: 'test', + extensions: [ + makeExtDef('a', 'enabled', 'other'), + makeExtDef('b', 'disabled', 'other'), + makeExtDef('c'), + ], + }), + ], + builtinExtensions: [], + parameters: [], + }), + ).toEqual([ + { + id: 'test/a', + extension: expect.objectContaining(aOverride), + attachTo: { id: 'other', input: 'default' }, + source: plugin, + disabled: false, + }, + { + id: 'test/b', + extension: expect.objectContaining(bOverride), + attachTo: { id: 'other', input: 'default' }, + source: plugin, + disabled: true, + }, + { + id: 'test/c', + extension: expect.objectContaining(cOverride), + attachTo: { id: 'root', input: 'default' }, + source: plugin, + disabled: false, + }, + ]); + }); + it('should use order from configuration when rather than overrides', () => { const result = resolveAppNodeSpecs({ features: [ - createPlugin({ + createFrontendPlugin({ id: 'test', extensions: [ makeExtDef('a', 'disabled'), @@ -353,11 +408,48 @@ describe('resolveAppNodeSpecs', () => { ]); }); + it('should use order from configuration when rather than modules', () => { + const result = resolveAppNodeSpecs({ + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + makeExtDef('a', 'disabled'), + makeExtDef('b', 'disabled'), + makeExtDef('c', 'disabled'), + ], + }), + createFrontendModule({ + pluginId: 'test', + extensions: [ + makeExtDef('c', 'disabled'), + makeExtDef('b', 'disabled'), + makeExtDef('a', 'disabled'), + ], + }), + ], + builtinExtensions: [], + parameters: ['test/b', 'test/c', 'test/a'].map(id => ({ + id, + disabled: false, + })), + }); + + expect(result.map(r => r.extension.id)).toEqual([ + 'test/b', + 'test/c', + 'test/a', + ]); + }); + it('throws an error when a forbidden extension is overridden by a plugin', () => { expect(() => resolveAppNodeSpecs({ features: [ - createPlugin({ id: 'test', extensions: [makeExtDef('forbidden')] }), + createFrontendPlugin({ + id: 'test', + extensions: [makeExtDef('forbidden')], + }), ], builtinExtensions: [], parameters: [], @@ -383,6 +475,28 @@ describe('resolveAppNodeSpecs', () => { ); }); + it('throws an error when a forbidden extension is overridden by module', () => { + expect(() => + resolveAppNodeSpecs({ + features: [ + createFrontendPlugin({ + id: 'forbidden', + extensions: [], + }), + createFrontendModule({ + pluginId: 'forbidden', + extensions: [makeExtDef()], + }), + ], + builtinExtensions: [], + parameters: [], + forbidden: new Set(['forbidden']), + }), + ).toThrow( + "It is forbidden to override the following extension(s): 'forbidden', which is done by a module for the following plugin(s): 'forbidden'", + ); + }); + it('throws an error when a forbidden extension is parametrized', () => { expect(() => resolveAppNodeSpecs({ diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index e31c70025d..bc3da52cd6 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -14,25 +14,29 @@ * limitations under the License. */ -import { - BackstagePlugin, - Extension, - ExtensionOverrides, - FrontendFeature, -} from '@backstage/frontend-plugin-api'; +import { Extension, ExtensionOverrides } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; import { ExtensionParameters } from './readAppExtensionsConfig'; import { AppNodeSpec } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createPlugin'; +import { + isInternalFrontendPlugin, + toInternalFrontendPlugin, +} from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + isInternalFrontendModule, + toInternalFrontendModule, +} from '../../../frontend-plugin-api/src/wiring/createFrontendModule'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +import { FrontendFeature } from '../wiring'; /** @internal */ export function resolveAppNodeSpecs(options: { features?: FrontendFeature[]; - builtinExtensions?: Extension[]; + builtinExtensions?: Extension[]; parameters?: Array; forbidden?: Set; }): AppNodeSpec[] { @@ -43,20 +47,30 @@ export function resolveAppNodeSpecs(options: { features = [], } = options; - const plugins = features.filter( - (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', - ); + const plugins = features.filter(isInternalFrontendPlugin); const overrides = features.filter( (f): f is ExtensionOverrides => f.$$type === '@backstage/ExtensionOverrides', ); + const modules = features.filter(isInternalFrontendModule); const pluginExtensions = plugins.flatMap(source => { - return toInternalBackstagePlugin(source).extensions.map(extension => ({ + return toInternalFrontendPlugin(source).extensions.map(extension => ({ ...extension, source, })); }); + const moduleExtensions = modules.flatMap(mod => + toInternalFrontendModule(mod).extensions.flatMap(extension => { + // Modules for plugins that are not installed are ignored + const source = plugins.find(p => p.id === mod.pluginId); + if (!source) { + return []; + } + + return [{ ...extension, source }]; + }), + ); const overrideExtensions = overrides.flatMap( override => toInternalExtensionOverrides(override).extensions, ); @@ -72,13 +86,23 @@ export function resolveAppNodeSpecs(options: { `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by the following plugin(s): ${pluginsStr}`, ); } - + if (moduleExtensions.some(({ id }) => forbidden.has(id))) { + const pluginsStr = moduleExtensions + .filter(({ id }) => forbidden.has(id)) + .map(({ source }) => `'${source.id}'`) + .join(', '); + const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', '); + throw new Error( + `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by a module for the following plugin(s): ${pluginsStr}`, + ); + } if (overrideExtensions.some(({ id }) => forbidden.has(id))) { const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', '); throw new Error( `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by one or more extension overrides`, ); } + const overrideExtensionIds = overrideExtensions.map(({ id }) => id); if (overrideExtensionIds.length !== new Set(overrideExtensionIds).size) { const counts = new Map(); @@ -149,6 +173,33 @@ export function resolveAppNodeSpecs(options: { } } + // Install all module overrides + for (const extension of moduleExtensions) { + const internalExtension = toInternalExtension(extension); + + // Check if our override is overriding an extension that already exists + const index = configuredExtensions.findIndex( + e => e.extension.id === extension.id, + ); + if (index !== -1) { + // Only implementation, attachment point and default disabled status are overridden, the source is kept + configuredExtensions[index].extension = internalExtension; + configuredExtensions[index].params.attachTo = internalExtension.attachTo; + configuredExtensions[index].params.disabled = internalExtension.disabled; + } else { + // Add the extension as a new one when not overriding an existing one + configuredExtensions.push({ + extension: internalExtension, + params: { + source: extension.source, + attachTo: internalExtension.attachTo, + disabled: internalExtension.disabled, + config: undefined, + }, + }); + } + } + const duplicatedExtensionIds = new Set(); const duplicatedExtensionData = configuredExtensions.reduce< Record> diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index d9b65555fe..0517e943aa 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { createExtension, Extension } from '@backstage/frontend-plugin-api'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + Extension, +} from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; @@ -23,8 +28,8 @@ const extension = resolveExtensionDefinition( createExtension({ name: 'test', attachTo: { id: 'nonexistent', input: 'nonexistent' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ) as Extension; @@ -165,4 +170,146 @@ describe('buildAppTree', () => { ]), ).toThrow("Unexpected duplicate extension id 'a'"); }); + + describe('redirects', () => { + it('should throw an error when theres a duplicate redirect target', () => { + const e1 = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'a', input: 'test' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e2 = resolveExtensionDefinition( + createExtension({ + name: 'test-2', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'a', input: 'test' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + expect(() => + resolveAppTree('a', [ + { ...baseSpec, id: 'a', extension: e1 }, + { ...baseSpec, id: 'b', extension: e2 }, + ]), + ).toThrow("Duplicate redirect target for input 'test' in extension 'b'"); + }); + + it('should set the correct attachment point for a redirect', () => { + const e1 = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'replace', input: 'me' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e2 = resolveExtensionDefinition( + createExtension({ + name: 'test-2', + attachTo: { id: 'replace', input: 'me' }, + output: [], + factory: () => [], + }), + ) as Extension; + + const tree = resolveAppTree('a', [ + { attachTo: e1.attachTo, id: 'a', extension: e1, disabled: false }, + { attachTo: e2.attachTo, id: 'b', extension: e2, disabled: false }, + ]); + + expect(tree.root).toMatchInlineSnapshot(` + { + "attachments": { + "test": [ + { + "attachments": undefined, + "id": "b", + "output": undefined, + }, + ], + }, + "id": "a", + "output": undefined, + } + `); + + expect(tree.orphans).toMatchInlineSnapshot(`[]`); + + expect(String(tree.root)).toMatchInlineSnapshot(` + " + test [ + + ] + " + `); + }); + + it('should not allow redirects for attachment points that already exist', () => { + const e1 = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'a', input: 'a' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement], { + replaces: [{ id: 'test-2', input: 'test' }], + }), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e2 = resolveExtensionDefinition( + createExtension({ + name: 'test-2', + attachTo: { id: 'b', input: 'b' }, + inputs: { + test: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [], + factory: () => [], + }), + ) as Extension; + + const e3 = resolveExtensionDefinition( + createExtension({ + name: 'test-3', + attachTo: { id: 'test-2', input: 'test' }, + output: [], + factory: () => [], + }), + ) as Extension; + + const tree = resolveAppTree('test-2', [ + { attachTo: e1.attachTo, id: e1.id, extension: e1, disabled: false }, + { attachTo: e2.attachTo, id: e2.id, extension: e2, disabled: false }, + { attachTo: e3.attachTo, id: e3.id, extension: e3, disabled: false }, + ]); + + expect(tree.nodes.get('test-3')?.edges.attachedTo?.node).toBe( + tree.nodes.get('test-2'), + ); + }); + }); }); diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.ts b/packages/frontend-app-api/src/tree/resolveAppTree.ts index 24948373bf..dc9985da30 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.ts @@ -21,6 +21,9 @@ import { AppNodeSpec, } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; + function indent(str: string) { return str.replace(/^/gm, ' '); } @@ -38,9 +41,7 @@ class SerializableAppNode implements AppNode { this.spec = spec; } - setParent(parent: SerializableAppNode) { - const input = this.spec.attachTo.input; - + setParent(parent: SerializableAppNode, input: string) { this.edges.attachedTo = { node: parent, input }; const parentInputEdges = parent.edges.attachments.get(input); @@ -87,6 +88,24 @@ class SerializableAppNode implements AppNode { } } +function makeRedirectKey(attachTo: { id: string; input: string }) { + return `${attachTo.id}%${attachTo.input}`; +} + +const isValidAttachmentPoint = ( + attachTo: { id: string; input: string }, + nodes: Map, +) => { + if (!nodes.has(attachTo.id)) { + return false; + } + + return ( + attachTo.input in + toInternalExtension(nodes.get(attachTo.id)!.spec.extension).inputs + ); +}; + /** * Build the app tree by iterating through all node specs and constructing the app * tree with all attachments in the same order as they appear in the input specs array. @@ -98,17 +117,7 @@ export function resolveAppTree( ): AppTree { const nodes = new Map(); - // A node with the provided rootNodeId must be found in the tree, and it must not be attached to anything - let rootNode: AppNode | undefined = undefined; - - // While iterating through the inputs specs we keep track of all nodes that were created - // before their parent, and attach them later when the parent is created. - // As we find the parents and attach the children, we remove them from this map. This means - // that after iterating through all input specs, this will be a map for each root node. - const orphansByParent = new Map< - string /* parentId */, - SerializableAppNode[] - >(); + const redirectTargetsByKey = new Map(); for (const spec of specs) { // The main check with a more helpful error message happens in resolveAppNodeSpecs @@ -119,28 +128,46 @@ export function resolveAppTree( const node = new SerializableAppNode(spec); nodes.set(spec.id, node); + const internal = toInternalExtension(spec.extension); + for (const [inputName, input] of Object.entries(internal.inputs)) { + if (input.replaces) { + for (const replace of input.replaces) { + const key = makeRedirectKey(replace); + if (redirectTargetsByKey.has(key)) { + throw new Error( + `Duplicate redirect target for input '${inputName}' in extension '${spec.id}'`, + ); + } + redirectTargetsByKey.set(key, { id: spec.id, input: inputName }); + } + } + } + } + + const orphans = new Array(); + + // A node with the provided rootNodeId must be found in the tree, and it must not be attached to anything + let rootNode: AppNode | undefined = undefined; + + for (const node of nodes.values()) { + const spec = node.spec; + // TODO: For now we simply ignore the attachTo spec of the root node, but it'd be cleaner if we could avoid defining it if (spec.id === rootNodeId) { rootNode = node; } else { - const parent = nodes.get(spec.attachTo.id); - if (parent) { - node.setParent(parent); - } else { - const orphanNodesForParent = orphansByParent.get(spec.attachTo.id); - if (orphanNodesForParent) { - orphanNodesForParent.push(node); - } else { - orphansByParent.set(spec.attachTo.id, [node]); - } - } - } + let attachTo = node.spec.attachTo; - const orphanedChildren = orphansByParent.get(spec.id); - if (orphanedChildren) { - orphansByParent.delete(spec.id); - for (const orphan of orphanedChildren) { - orphan.setParent(node); + if (!isValidAttachmentPoint(attachTo, nodes)) { + attachTo = + redirectTargetsByKey.get(makeRedirectKey(attachTo)) ?? attachTo; + } + + const parent = nodes.get(attachTo.id); + if (parent) { + node.setParent(parent, attachTo.input); + } else { + orphans.push(node); } } } @@ -152,6 +179,6 @@ export function resolveAppTree( return { root: rootNode, nodes, - orphans: Array.from(orphansByParent.values()).flat(), + orphans, }; } diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx deleted file mode 100644 index 94254a2e01..0000000000 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ /dev/null @@ -1,332 +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 { - AppTreeApi, - appTreeApiRef, - coreExtensionData, - createExtension, - createExtensionOverrides, - createPageExtension, - createPlugin, - createThemeExtension, -} from '@backstage/frontend-plugin-api'; -import { screen, waitFor } from '@testing-library/react'; -import { CreateAppFeatureLoader, createApp } from './createApp'; -import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; -import React from 'react'; -import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; - -describe('createApp', () => { - it('should allow themes to be installed', async () => { - const app = createApp({ - configLoader: async () => ({ - config: new MockConfigApi({ - app: { - extensions: [ - { 'theme:app/light': false }, - { 'theme:app/dark': false }, - ], - }, - }), - }), - features: [ - createPlugin({ - id: 'test', - extensions: [ - createThemeExtension({ - id: 'derp', - title: 'Derp', - variant: 'dark', - Provider: () =>
Derp
, - }), - ], - }), - ], - }); - - await renderWithEffects(app.createRoot()); - - await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); - }); - - it('should deduplicate features keeping the last received one', async () => { - const duplicatedFeatureId = 'test'; - const app = createApp({ - configLoader: async () => ({ config: new MockConfigApi({}) }), - features: [ - createPlugin({ - id: duplicatedFeatureId, - extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
First Page
, - }), - ], - }), - createPlugin({ - id: duplicatedFeatureId, - extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
Last Page
, - }), - ], - }), - ], - }); - - await renderWithEffects(app.createRoot()); - - await waitFor(() => - expect(screen.queryByText('First Page')).not.toBeInTheDocument(), - ); - await waitFor(() => - expect(screen.getByText('Last Page')).toBeInTheDocument(), - ); - }); - - it('should support feature loaders', async () => { - const loader: CreateAppFeatureLoader = { - getLoaderName() { - return 'test-loader'; - }, - async load({ config }) { - return { - features: [ - createPlugin({ - id: 'test', - extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () =>
{config.getString('key')}
, - }), - ], - }), - ], - }; - }, - }; - - const app = createApp({ - configLoader: async () => ({ - config: new MockConfigApi({ key: 'config-value' }), - }), - features: [loader], - }); - - await renderWithEffects(app.createRoot()); - - await expect( - screen.findByText('config-value'), - ).resolves.toBeInTheDocument(); - }); - - it('should propagate errors thrown by feature loaders', async () => { - const loader: CreateAppFeatureLoader = { - getLoaderName() { - return 'test-loader'; - }, - async load() { - throw new TypeError('boom'); - }, - }; - - const app = createApp({ - configLoader: async () => ({ - config: new MockConfigApi({}), - }), - features: [loader], - }); - - await expect( - renderWithEffects(app.createRoot()), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`, - ); - }); - - it('should register feature flags', async () => { - const app = createApp({ - configLoader: async () => ({ config: new MockConfigApi({}) }), - features: [ - createPlugin({ - id: 'test', - featureFlags: [{ name: 'test-1' }], - extensions: [ - createExtension({ - name: 'first', - attachTo: { id: 'app', input: 'root' }, - output: { element: coreExtensionData.reactElement }, - factory() { - const Component = () => { - const flagsApi = useApi(featureFlagsApiRef); - return ( -
- Flags:{' '} - {flagsApi - .getRegisteredFlags() - .map(flag => `${flag.name} from '${flag.pluginId}'`) - .join(', ')} -
- ); - }; - return { element: }; - }, - }), - ], - }), - createExtensionOverrides({ - featureFlags: [{ name: 'test-2' }], - extensions: [ - createExtension({ - namespace: 'app', - name: 'root', - attachTo: { id: 'app', input: 'root' }, - disabled: true, - output: {}, - factory: () => ({}), - }), - ], - }), - ], - }); - - await renderWithEffects(app.createRoot()); - - await expect( - screen.findByText("Flags: test-1 from 'test', test-2 from ''"), - ).resolves.toBeInTheDocument(); - }); - - it('should make the app structure available through the AppTreeApi', async () => { - let appTreeApi: AppTreeApi | undefined = undefined; - - const app = createApp({ - configLoader: async () => ({ config: new MockConfigApi({}) }), - features: [ - createPlugin({ - id: 'my-plugin', - extensions: [ - createPageExtension({ - defaultPath: '/', - loader: async () => { - const Component = () => { - appTreeApi = useApi(appTreeApiRef); - return
My Plugin Page
; - }; - return ; - }, - }), - ], - }), - ], - }); - - await renderWithEffects(app.createRoot()); - - expect(appTreeApi).toBeDefined(); - const { tree } = appTreeApi!.getTree(); - - expect(String(tree.root)).toMatchInlineSnapshot(` - " - root [ - - children [ - - content [ - - routes [ - - ] - - ] - nav [ - - ] - - ] - elements [ - - - ] - - ] - components [ - - - - ] - themes [ - - - ] - apis [ - - - - - - - - - - - - - - - - - - - ] - " - `); - }); - - it('should use "Loading..." as the default suspense fallback', async () => { - const app = createApp({ - configLoader: () => new Promise(() => {}), - }); - - await renderWithEffects(app.createRoot()); - - await expect(screen.findByText('Loading...')).resolves.toBeInTheDocument(); - }); - - it('should use no suspense fallback if the "loadingComponent" is null', async () => { - const app = createApp({ - configLoader: () => new Promise(() => {}), - loadingComponent: null, - }); - - await renderWithEffects(app.createRoot()); - - expect(screen.queryByText('Loading...')).toBeNull(); - }); - - it('should use a custom "loadingComponent"', async () => { - const app = createApp({ - configLoader: () => new Promise(() => {}), - loadingComponent: "Custom loading message", - }); - - await renderWithEffects(app.createRoot()); - - expect(screen.queryByText('Custom loading message')).toBeNull(); - }); -}); diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx deleted file mode 100644 index d82d0ca782..0000000000 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ /dev/null @@ -1,466 +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, { JSX, ReactNode } from 'react'; -import { ConfigReader } from '@backstage/config'; -import { - AppTree, - appTreeApiRef, - componentsApiRef, - coreExtensionData, - createApiExtension, - createThemeExtension, - createTranslationExtension, - FrontendFeature, - IconBundleBlueprint, - iconsApiRef, - RouteResolutionApi, - routeResolutionApiRef, -} from '@backstage/frontend-plugin-api'; -import { App } from '../extensions/App'; -import { AppRoutes } from '../extensions/AppRoutes'; -import { AppLayout } from '../extensions/AppLayout'; -import { AppNav } from '../extensions/AppNav'; -import { - AnyApiFactory, - ApiHolder, - appThemeApiRef, - ConfigApi, - configApiRef, - IconComponent, - featureFlagsApiRef, - identityApiRef, - AppTheme, - errorApiRef, - discoveryApiRef, - fetchApiRef, -} from '@backstage/core-plugin-api'; -import { getAvailableFeatures } from './discovery'; -import { - ApiFactoryRegistry, - ApiProvider, - ApiResolver, - AppThemeSelector, -} from '@backstage/core-app-api'; - -// TODO: Get rid of all of these -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { isProtectedApp } from '../../../core-app-api/src/app/isProtectedApp'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { overrideBaseUrlConfigs } from '../../../core-app-api/src/app/overrideBaseUrlConfigs'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppLanguageSelector } from '../../../core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { apis as defaultApis } from '../../../app-defaults/src/defaults'; -import { DarkTheme, LightTheme } from '../extensions/themes'; -import { - oauthRequestDialogAppRootElement, - alertDisplayAppRootElement, -} from '../extensions/elements'; -import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode'; -import { - appLanguageApiRef, - translationApiRef, -} from '@backstage/core-plugin-api/alpha'; -import { CreateAppRouteBinder } from '../routing'; -import { RouteResolver } from '../routing/RouteResolver'; -import { resolveRouteBindings } from '../routing/resolveRouteBindings'; -import { collectRouteIds } from '../routing/collectRouteIds'; -import { createAppTree } from '../tree'; -import { - DefaultProgressComponent, - DefaultErrorBoundaryComponent, - DefaultNotFoundErrorPageComponent, -} from '../extensions/components'; -import { InternalAppContext } from './InternalAppContext'; -import { AppRoot } from '../extensions/AppRoot'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createPlugin'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; -import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi'; -import { DefaultIconsApi } from '../apis/implementations/IconsApi'; -import { stringifyError } from '@backstage/errors'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { icons as defaultIcons } from '../../../app-defaults/src/defaults'; -import { getBasePath } from '../routing/getBasePath'; - -const DefaultApis = defaultApis.map(factory => createApiExtension({ factory })); - -export const builtinExtensions = [ - App, - AppRoot, - AppRoutes, - AppNav, - AppLayout, - DefaultProgressComponent, - DefaultErrorBoundaryComponent, - DefaultNotFoundErrorPageComponent, - LightTheme, - DarkTheme, - oauthRequestDialogAppRootElement, - alertDisplayAppRootElement, - ...DefaultApis, -].map(def => resolveExtensionDefinition(def)); - -function deduplicateFeatures( - allFeatures: FrontendFeature[], -): FrontendFeature[] { - // Start by removing duplicates by reference - const features = Array.from(new Set(allFeatures)); - - // Plugins are deduplicated by ID, last one wins - const seenIds = new Set(); - return features - .reverse() - .filter(feature => { - if (feature.$$type !== '@backstage/BackstagePlugin') { - return true; - } - if (seenIds.has(feature.id)) { - return false; - } - seenIds.add(feature.id); - return true; - }) - .reverse(); -} - -/** - * A source of dynamically loaded frontend features. - * - * @public - */ -export interface CreateAppFeatureLoader { - /** - * Returns name of this loader. suitable for showing to users. - */ - getLoaderName(): string; - - /** - * Loads a number of features dynamically. - */ - load(options: { config: ConfigApi }): Promise<{ - features: FrontendFeature[]; - }>; -} - -/** @public */ -export function createApp(options?: { - /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ - icons?: { [key in string]: IconComponent }; - features?: (FrontendFeature | CreateAppFeatureLoader)[]; - configLoader?: () => Promise<{ config: ConfigApi }>; - bindRoutes?(context: { bind: CreateAppRouteBinder }): void; - /** - * The component to render while loading the app (waiting for config, features, etc) - * - * Is the text "Loading..." by default. - * If set to "null" then no loading fallback component is rendered. * - */ - loadingComponent?: ReactNode; -}): { - createRoot(): JSX.Element; -} { - let suspenseFallback = options?.loadingComponent; - if (suspenseFallback === undefined) { - suspenseFallback = 'Loading...'; - } - - async function appLoader() { - const config = - (await options?.configLoader?.().then(c => c.config)) ?? - ConfigReader.fromConfigs( - overrideBaseUrlConfigs(defaultConfigLoaderSync()), - ); - - const discoveredFeatures = getAvailableFeatures(config); - - const providedFeatures: FrontendFeature[] = []; - for (const entry of options?.features ?? []) { - if ('load' in entry) { - try { - const result = await entry.load({ config }); - providedFeatures.push(...result.features); - } catch (e) { - throw new Error( - `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError( - e, - )}`, - ); - } - } else { - providedFeatures.push(entry); - } - } - - const app = createSpecializedApp({ - icons: options?.icons, - config, - features: [...discoveredFeatures, ...providedFeatures], - bindRoutes: options?.bindRoutes, - }).createRoot(); - - return { default: () => app }; - } - - return { - createRoot() { - const LazyApp = React.lazy(appLoader); - return ( - - - - ); - }, - }; -} - -/** - * Synchronous version of {@link createApp}, expecting all features and - * config to have been loaded already. - * - * @public - */ -export function createSpecializedApp(options?: { - /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ - icons?: { [key in string]: IconComponent }; - features?: FrontendFeature[]; - config?: ConfigApi; - bindRoutes?(context: { bind: CreateAppRouteBinder }): void; -}): { createRoot(): JSX.Element } { - const { - features: duplicatedFeatures = [], - config = new ConfigReader({}, 'empty-config'), - } = options ?? {}; - - const features = deduplicateFeatures(duplicatedFeatures); - - const tree = createAppTree({ - features, - builtinExtensions, - config, - }); - - const routeInfo = extractRouteInfoFromAppNode(tree.root); - const routeBindings = resolveRouteBindings( - options?.bindRoutes, - config, - collectRouteIds(features), - ); - - const appIdentityProxy = new AppIdentityProxy(); - const apiHolder = createApiHolder( - tree, - config, - appIdentityProxy, - new RouteResolver( - routeInfo.routePaths, - routeInfo.routeParents, - routeInfo.routeObjects, - routeBindings, - getBasePath(config), - ), - options?.icons, - ); - - if (isProtectedApp()) { - const discoveryApi = apiHolder.get(discoveryApiRef); - const errorApi = apiHolder.get(errorApiRef); - const fetchApi = apiHolder.get(fetchApiRef); - if (!discoveryApi || !errorApi || !fetchApi) { - throw new Error( - 'App is running in protected mode but missing required APIs', - ); - } - appIdentityProxy.enableCookieAuth({ - discoveryApi, - errorApi, - fetchApi, - }); - } - - const featureFlagApi = apiHolder.get(featureFlagsApiRef); - if (featureFlagApi) { - for (const feature of features) { - if (feature.$$type === '@backstage/BackstagePlugin') { - toInternalBackstagePlugin(feature).featureFlags.forEach(flag => - featureFlagApi.registerFlag({ - name: flag.name, - pluginId: feature.id, - }), - ); - } - if (feature.$$type === '@backstage/ExtensionOverrides') { - toInternalExtensionOverrides(feature).featureFlags.forEach(flag => - featureFlagApi.registerFlag({ name: flag.name, pluginId: '' }), - ); - } - } - } - - const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement); - - const AppComponent = () => ( - - - - {rootEl} - - - - ); - - return { - createRoot() { - return ; - }, - }; -} - -function createApiHolder( - tree: AppTree, - configApi: ConfigApi, - appIdentityProxy: AppIdentityProxy, - routeResolutionApi: RouteResolutionApi, - icons?: { [key in string]: IconComponent }, -): ApiHolder { - const factoryRegistry = new ApiFactoryRegistry(); - - const pluginApis = - tree.root.edges.attachments - .get('apis') - ?.map(e => e.instance?.getData(createApiExtension.factoryDataRef)) - .filter((x): x is AnyApiFactory => !!x) ?? []; - - const themeExtensions = - tree.root.edges.attachments - .get('themes') - ?.map(e => e.instance?.getData(createThemeExtension.themeDataRef)) - .filter((x): x is AppTheme => !!x) ?? []; - - const translationResources = - tree.root.edges.attachments - .get('translations') - ?.map(e => - e.instance?.getData(createTranslationExtension.translationDataRef), - ) - .filter( - (x): x is typeof createTranslationExtension.translationDataRef.T => !!x, - ) ?? []; - - const extensionIcons = tree.root.edges.attachments - .get('icons') - ?.map(e => e.instance?.getData(IconBundleBlueprint.dataRefs.icons)) - .reduce((acc, bundle) => ({ ...acc, ...bundle }), {}); - - for (const factory of pluginApis) { - factoryRegistry.register('default', factory); - } - - // TODO: properly discovery feature flags, maybe rework the whole thing - factoryRegistry.register('default', { - api: featureFlagsApiRef, - deps: {}, - factory: () => new LocalStorageFeatureFlags(), - }); - - factoryRegistry.register('static', { - api: identityApiRef, - deps: {}, - factory: () => appIdentityProxy, - }); - - factoryRegistry.register('static', { - api: appTreeApiRef, - deps: {}, - factory: () => ({ - getTree: () => ({ tree }), - }), - }); - - factoryRegistry.register('static', { - api: routeResolutionApiRef, - deps: {}, - factory: () => routeResolutionApi, - }); - - factoryRegistry.register('static', { - api: componentsApiRef, - deps: {}, - factory: () => DefaultComponentsApi.fromTree(tree), - }); - - factoryRegistry.register('static', { - api: iconsApiRef, - deps: {}, - factory: () => - new DefaultIconsApi({ ...defaultIcons, ...extensionIcons, ...icons }), - }); - - factoryRegistry.register('static', { - api: appThemeApiRef, - deps: {}, - // TODO: add extension for registering themes - factory: () => AppThemeSelector.createWithStorage(themeExtensions), - }); - - factoryRegistry.register('static', { - api: appLanguageApiRef, - deps: {}, - factory: () => AppLanguageSelector.createWithStorage(), - }); - - factoryRegistry.register('static', { - api: configApiRef, - deps: {}, - factory: () => configApi, - }); - - factoryRegistry.register('static', { - api: appLanguageApiRef, - deps: {}, - factory: () => AppLanguageSelector.createWithStorage(), - }); - - factoryRegistry.register('static', { - api: translationApiRef, - deps: { languageApi: appLanguageApiRef }, - factory: ({ languageApi }) => - I18nextTranslationApi.create({ - languageApi, - resources: translationResources, - }), - }); - - ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis()); - - return new ApiResolver(factoryRegistry); -} diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx new file mode 100644 index 0000000000..e98014f273 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -0,0 +1,283 @@ +/* + * 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 { + AppTreeApi, + appTreeApiRef, + coreExtensionData, + createExtension, + createFrontendPlugin, + ApiBlueprint, + createRouteRef, + createExternalRouteRef, + createExtensionInput, + useRouteRef, +} from '@backstage/frontend-plugin-api'; +import { screen, render } from '@testing-library/react'; +import { createSpecializedApp } from './createSpecializedApp'; +import { MockConfigApi } from '@backstage/test-utils'; +import React from 'react'; +import { + configApiRef, + createApiFactory, + featureFlagsApiRef, +} from '@backstage/core-plugin-api'; +import { MemoryRouter } from 'react-router-dom'; +import { ApiProvider } from '@backstage/core-app-api'; + +describe('createSpecializedApp', () => { + it('should render the root app', () => { + const app = createSpecializedApp({ + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
Test
)], + }), + ], + }), + ], + }); + + render(app.createRoot()); + + expect(screen.getByText('Test')).toBeInTheDocument(); + }); + + it('should deduplicate features keeping the last received one', () => { + const app = createSpecializedApp({ + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
Test 1
), + ], + }), + ], + }), + createFrontendPlugin({ + id: 'test', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
Test 2
), + ], + }), + ], + }), + ], + }); + + render(app.createRoot()); + + expect(screen.getByText('Test 2')).toBeInTheDocument(); + }); + + it('should forward config', () => { + const app = createSpecializedApp({ + config: new MockConfigApi({ test: 'foo' }), + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => [ + coreExtensionData.reactElement( +
Test {apis.get(configApiRef)!.getString('test')}
, + ), + ], + }), + ], + }), + ], + }); + + render(app.createRoot()); + + expect(screen.getByText('Test foo')).toBeInTheDocument(); + }); + + it('should support APIs and feature flags', async () => { + const flags = new Array<{ name: string; pluginId: string }>(); + const app = createSpecializedApp({ + features: [ + createFrontendPlugin({ + id: 'test', + featureFlags: [{ name: 'a' }, { name: 'b' }], + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => [ + coreExtensionData.reactElement( +
+ flags: + {apis + .get(featureFlagsApiRef)! + .getRegisteredFlags() + .map(f => `${f.pluginId}=${f.name}`) + .join(',')} +
, + ), + ], + }), + ApiBlueprint.make({ + params: { + factory: createApiFactory(featureFlagsApiRef, { + registerFlag(flag) { + flags.push(flag); + }, + getRegisteredFlags() { + return flags; + }, + } as typeof featureFlagsApiRef.T), + }, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + expect(screen.getByText('flags:test=a,test=b')).toBeInTheDocument(); + }); + + it('should make the app structure available through the AppTreeApi', async () => { + let appTreeApi: AppTreeApi | undefined = undefined; + + createSpecializedApp({ + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: ({ apis }) => { + appTreeApi = apis.get(appTreeApiRef); + return [coreExtensionData.reactElement(
)]; + }, + }), + ], + }), + ], + }); + + expect(String(appTreeApi!.getTree().tree.root)).toMatchInlineSnapshot(` + " + app [ + + ] + " + `); + }); + + it('should support route bindings', async () => { + const routeRef = createRouteRef(); + const extRouteRef = createExternalRouteRef(); + + const pluginA = createFrontendPlugin({ + id: 'a', + externalRoutes: { + ext: extRouteRef, + }, + extensions: [ + createExtension({ + name: 'parent', + attachTo: { id: 'root', input: 'app' }, + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [coreExtensionData.reactElement], + factory: ({ apis, inputs }) => { + return [ + coreExtensionData.reactElement( + + + {inputs.children.map(i => ( + + {i.get(coreExtensionData.reactElement)} + + ))} + + , + ), + ]; + }, + }), + createExtension({ + name: 'child', + attachTo: { id: 'a/parent', input: 'children' }, + output: [coreExtensionData.reactElement], + factory: () => { + const Component = () => { + const link = useRouteRef(extRouteRef); + return
link: {link?.() ?? 'none'}
; + }; + return [coreExtensionData.reactElement()]; + }, + }), + ], + }); + const pluginB = createFrontendPlugin({ + id: 'b', + routes: { + root: routeRef, + }, + extensions: [ + createExtension({ + name: 'child', + attachTo: { id: 'a/parent', input: 'children' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef, + ], + factory: () => { + return [ + coreExtensionData.reactElement(
), + coreExtensionData.routePath('/test'), + coreExtensionData.routeRef(routeRef), + ]; + }, + }), + ], + }); + + render( + createSpecializedApp({ + features: [pluginA, pluginB], + bindRoutes({ bind }) { + bind(pluginA.externalRoutes, { ext: pluginB.routes.root }); + }, + }).createRoot(), + ); + + expect(screen.getByText('link: /test')).toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx new file mode 100644 index 0000000000..612befc597 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -0,0 +1,319 @@ +/* + * 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, { JSX } from 'react'; +import { ConfigReader } from '@backstage/config'; +import { + ApiBlueprint, + AppTree, + AppTreeApi, + appTreeApiRef, + coreExtensionData, + RouteRef, + ExternalRouteRef, + SubRouteRef, + AnyRouteRefParams, + RouteFunc, + RouteResolutionApiResolveOptions, + RouteResolutionApi, + createApiFactory, + routeResolutionApiRef, +} from '@backstage/frontend-plugin-api'; +import { + AnyApiFactory, + ApiHolder, + ConfigApi, + configApiRef, + featureFlagsApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api'; + +// TODO: Get rid of all of these + +import { + createApp as _createApp, + CreateAppFeatureLoader as _CreateAppFeatureLoader, +} from '@backstage/frontend-defaults'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; + +import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode'; + +import { CreateAppRouteBinder } from '../routing'; +import { RouteResolver } from '../routing/RouteResolver'; +import { resolveRouteBindings } from '../routing/resolveRouteBindings'; +import { collectRouteIds } from '../routing/collectRouteIds'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + toInternalFrontendPlugin, + isInternalFrontendPlugin, +} from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + toInternalFrontendModule, + isInternalFrontendModule, +} from '../../../frontend-plugin-api/src/wiring/createFrontendModule'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; +import { getBasePath } from '../routing/getBasePath'; +import { Root } from '../extensions/Root'; +import { resolveAppTree } from '../tree/resolveAppTree'; +import { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs'; +import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig'; +import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { BackstageRouteObject } from '../routing/types'; +import { FrontendFeature } from './types'; + +function deduplicateFeatures( + allFeatures: FrontendFeature[], +): FrontendFeature[] { + // Start by removing duplicates by reference + const features = Array.from(new Set(allFeatures)); + + // Plugins are deduplicated by ID, last one wins + const seenIds = new Set(); + return features + .reverse() + .filter(feature => { + if (!isInternalFrontendPlugin(feature)) { + return true; + } + if (seenIds.has(feature.id)) { + return false; + } + seenIds.add(feature.id); + return true; + }) + .reverse(); +} + +// Helps delay callers from reaching out to the API before the app tree has been materialized +class AppTreeApiProxy implements AppTreeApi { + #safeToUse: boolean = false; + + constructor(private readonly tree: AppTree) {} + + getTree() { + if (!this.#safeToUse) { + throw new Error( + `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, + ); + } + return { tree: this.tree }; + } + + initialize() { + this.#safeToUse = true; + } +} + +// Helps delay callers from reaching out to the API before the app tree has been materialized +class RouteResolutionApiProxy implements RouteResolutionApi { + #delegate: RouteResolutionApi | undefined; + #routeObjects: BackstageRouteObject[] | undefined; + + constructor( + private readonly tree: AppTree, + private readonly routeBindings: Map< + ExternalRouteRef, + RouteRef | SubRouteRef + >, + private readonly basePath: string, + ) {} + + resolve( + anyRouteRef: + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: RouteResolutionApiResolveOptions, + ): RouteFunc | undefined { + if (!this.#delegate) { + throw new Error( + `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`, + ); + } + + return this.#delegate.resolve(anyRouteRef, options); + } + + initialize() { + const routeInfo = extractRouteInfoFromAppNode(this.tree.root); + + this.#delegate = new RouteResolver( + routeInfo.routePaths, + routeInfo.routeParents, + routeInfo.routeObjects, + this.routeBindings, + this.basePath, + ); + this.#routeObjects = routeInfo.routeObjects; + + return routeInfo; + } + + getRouteObjects() { + return this.#routeObjects; + } +} + +/** + * @public + * @deprecated Import from `@backstage/frontend-defaults` instead. + */ +export const createApp = _createApp; + +/** + * @public + * @deprecated Import from `@backstage/frontend-defaults` instead. + */ +export type CreateAppFeatureLoader = _CreateAppFeatureLoader; + +/** + * Creates an empty app without any default features. This is a low-level API is + * intended for use in tests or specialized setups. Typically wou want to use + * `createApp` from `@backstage/frontend-defaults` instead. + * + * @public + */ +export function createSpecializedApp(options?: { + features?: FrontendFeature[]; + config?: ConfigApi; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; +}): { createRoot(): JSX.Element } { + const config = options?.config ?? new ConfigReader({}, 'empty-config'); + const features = deduplicateFeatures(options?.features ?? []); + + const tree = resolveAppTree( + 'root', + resolveAppNodeSpecs({ + features, + builtinExtensions: [ + resolveExtensionDefinition(Root, { namespace: 'root' }), + ], + parameters: readAppExtensionsConfig(config), + forbidden: new Set(['root']), + }), + ); + + const factories = createApiFactories({ tree }); + const appTreeApi = new AppTreeApiProxy(tree); + const routeResolutionApi = new RouteResolutionApiProxy( + tree, + resolveRouteBindings( + options?.bindRoutes, + config, + collectRouteIds(features), + ), + getBasePath(config), + ); + + const appIdentityProxy = new AppIdentityProxy(); + const apiHolder = createApiHolder({ + factories, + staticFactories: [ + createApiFactory(appTreeApiRef, appTreeApi), + createApiFactory(configApiRef, config), + createApiFactory(routeResolutionApiRef, routeResolutionApi), + createApiFactory(identityApiRef, appIdentityProxy), + ], + }); + + const featureFlagApi = apiHolder.get(featureFlagsApiRef); + if (featureFlagApi) { + for (const feature of features) { + if (isInternalFrontendPlugin(feature)) { + toInternalFrontendPlugin(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + pluginId: feature.id, + }), + ); + } + if (isInternalFrontendModule(feature)) { + toInternalFrontendModule(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + pluginId: feature.pluginId, + }), + ); + } + if (feature.$$type === '@backstage/ExtensionOverrides') { + toInternalExtensionOverrides(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ name: flag.name, pluginId: '' }), + ); + } + } + } + + // Now instantiate the entire tree, which will skip anything that's already been instantiated + instantiateAppNodeTree(tree.root, apiHolder); + + routeResolutionApi.initialize(); + appTreeApi.initialize(); + + const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement); + + const AppComponent = () => rootEl; + + return { + createRoot() { + return ; + }, + }; +} + +function createApiFactories(options: { tree: AppTree }): AnyApiFactory[] { + const emptyApiHolder = ApiRegistry.from([]); + const factories = new Array(); + + for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) { + instantiateAppNodeTree(apiNode, emptyApiHolder); + const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory); + if (!apiFactory) { + throw new Error( + `No API factory found in for extension ${apiNode.spec.id}`, + ); + } + factories.push(apiFactory); + } + + return factories; +} + +function createApiHolder(options: { + factories: AnyApiFactory[]; + staticFactories: AnyApiFactory[]; +}): ApiHolder { + const factoryRegistry = new ApiFactoryRegistry(); + + for (const factory of options.factories) { + factoryRegistry.register('default', factory); + } + + for (const factory of options.staticFactories) { + factoryRegistry.register('static', factory); + } + + ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis()); + + return new ApiResolver(factoryRegistry); +} diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index d1498514c8..d76055f63b 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -18,4 +18,5 @@ export { createApp, createSpecializedApp, type CreateAppFeatureLoader, -} from './createApp'; +} from './createSpecializedApp'; +export * from './types'; diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/frontend-app-api/src/wiring/types.ts similarity index 63% rename from packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts rename to packages/frontend-app-api/src/wiring/types.ts index 988b907b4c..fb887534e3 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/frontend-app-api/src/wiring/types.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { FrontendModule, FrontendPlugin } from '@backstage/frontend-plugin-api'; -// 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; +/** @public */ +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + // TODO(blam): This is just forwards backwards compatibility, remove after v1.31.0 + | { $$type: '@backstage/ExtensionOverrides' } + | { $$type: '@backstage/BackstagePlugin' }; diff --git a/packages/backend-tasks/.eslintrc.js b/packages/frontend-defaults/.eslintrc.js similarity index 100% rename from packages/backend-tasks/.eslintrc.js rename to packages/frontend-defaults/.eslintrc.js diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md new file mode 100644 index 0000000000..79ea0d79eb --- /dev/null +++ b/packages/frontend-defaults/CHANGELOG.md @@ -0,0 +1,18 @@ +# @backstage/frontend-defaults + +## 0.1.0-next.0 + +### Minor Changes + +- 7c80650: Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`. + +### Patch Changes + +- 7d19cd5: Added a new `CreateAppOptions` type for the `createApp` options. +- 7d19cd5: Added `createPublicSignInApp`, used to creating apps for the public entry point. +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 diff --git a/packages/frontend-defaults/README.md b/packages/frontend-defaults/README.md new file mode 100644 index 0000000000..2cca17ee85 --- /dev/null +++ b/packages/frontend-defaults/README.md @@ -0,0 +1,9 @@ +# @backstage/frontend-defaults + +**The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is part of is in alpha, and we do not yet recommend using it for production deployments** + +This package provides the high-level APIs used to create Backstage frontend applications with the default setup. For more information, see the [documentation on how to build Backstage frontend applications](https://backstage.io/docs/frontend-system/building-apps/index). + +## Documentation + +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/frontend-defaults/api-report.md b/packages/frontend-defaults/api-report.md new file mode 100644 index 0000000000..b99232b784 --- /dev/null +++ b/packages/frontend-defaults/api-report.md @@ -0,0 +1,43 @@ +## API Report File for "@backstage/frontend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; +import { FrontendFeature } from '@backstage/frontend-app-api'; +import { JSX as JSX_2 } from 'react'; +import { default as React_2 } from 'react'; +import { ReactNode } from 'react'; + +// @public +export function createApp(options?: CreateAppOptions): { + createRoot(): JSX_2.Element; +}; + +// @public +export interface CreateAppFeatureLoader { + getLoaderName(): string; + load(options: { config: ConfigApi }): Promise<{ + features: FrontendFeature[]; + }>; +} + +// @public +export interface CreateAppOptions { + // (undocumented) + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; + // (undocumented) + configLoader?: () => Promise<{ + config: ConfigApi; + }>; + // (undocumented) + features?: (FrontendFeature | CreateAppFeatureLoader)[]; + loadingComponent?: ReactNode; +} + +// @public +export function createPublicSignInApp(options?: CreateAppOptions): { + createRoot(): React_2.JSX.Element; +}; +``` diff --git a/packages/frontend-defaults/catalog-info.yaml b/packages/frontend-defaults/catalog-info.yaml new file mode 100644 index 0000000000..cd0ac9b79a --- /dev/null +++ b/packages/frontend-defaults/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-frontend-defaults + title: '@backstage/frontend-defaults' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json new file mode 100644 index 0000000000..7f00371525 --- /dev/null +++ b/packages/frontend-defaults/package.json @@ -0,0 +1,52 @@ +{ + "name": "@backstage/frontend-defaults", + "version": "0.1.0-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/frontend-defaults" + }, + "backstage": { + "role": "web-library" + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^15.0.0" + }, + "files": [ + "dist" + ], + "dependencies": { + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/frontend-app-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-app": "workspace:^", + "@react-hookz/web": "^24.0.0", + "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + } +} diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx new file mode 100644 index 0000000000..2890700653 --- /dev/null +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -0,0 +1,433 @@ +/* + * 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 { + AppTreeApi, + appTreeApiRef, + coreExtensionData, + createExtension, + PageBlueprint, + createFrontendPlugin, + ThemeBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { screen, waitFor } from '@testing-library/react'; +import { CreateAppFeatureLoader, createApp } from './createApp'; +import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; +import React from 'react'; +import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; +import appPlugin from '@backstage/plugin-app'; + +describe('createApp', () => { + it('should allow themes to be installed', async () => { + const app = createApp({ + configLoader: async () => ({ + config: new MockConfigApi({ + app: { + extensions: [ + { 'theme:app/light': false }, + { 'theme:app/dark': false }, + ], + }, + }), + }), + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + ThemeBlueprint.make({ + name: 'derp', + params: { + theme: { + id: 'derp', + title: 'Derp', + variant: 'dark', + Provider: () =>
Derp
, + }, + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); + }); + + it('should deduplicate features keeping the last received one', async () => { + const duplicatedFeatureId = 'test'; + const app = createApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createFrontendPlugin({ + id: duplicatedFeatureId, + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
First Page
, + }, + }), + ], + }), + createFrontendPlugin({ + id: duplicatedFeatureId, + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
Last Page
, + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + await waitFor(() => + expect(screen.queryByText('First Page')).not.toBeInTheDocument(), + ); + await waitFor(() => + expect(screen.getByText('Last Page')).toBeInTheDocument(), + ); + }); + + it('should support feature loaders', async () => { + const loader: CreateAppFeatureLoader = { + getLoaderName() { + return 'test-loader'; + }, + async load({ config }) { + return { + features: [ + createFrontendPlugin({ + id: 'test', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () =>
{config.getString('key')}
, + }, + }), + ], + }), + ], + }; + }, + }; + + const app = createApp({ + configLoader: async () => ({ + config: new MockConfigApi({ key: 'config-value' }), + }), + features: [appPlugin, loader], + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText('config-value'), + ).resolves.toBeInTheDocument(); + }); + + it('should propagate errors thrown by feature loaders', async () => { + const loader: CreateAppFeatureLoader = { + getLoaderName() { + return 'test-loader'; + }, + async load() { + throw new TypeError('boom'); + }, + }; + + const app = createApp({ + configLoader: async () => ({ + config: new MockConfigApi({}), + }), + features: [loader], + }); + + await expect( + renderWithEffects(app.createRoot()), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`, + ); + }); + + it('should register feature flags', async () => { + const app = createApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + appPlugin.withOverrides({ + extensions: [ + appPlugin + .getExtension('app/root') + .override({ disabled: true, factory: orig => orig() }), + ], + }), + createFrontendPlugin({ + id: 'test', + featureFlags: [{ name: 'test-1' }], + extensions: [ + createExtension({ + name: 'first', + attachTo: { id: 'app', input: 'root' }, + output: [coreExtensionData.reactElement], + factory() { + const Component = () => { + const flagsApi = useApi(featureFlagsApiRef); + return ( +
+ Flags:{' '} + {flagsApi + .getRegisteredFlags() + .map(flag => `${flag.name} from '${flag.pluginId}'`) + .join(', ')} +
+ ); + }; + return [coreExtensionData.reactElement()]; + }, + }), + ], + }), + createFrontendPlugin({ + id: 'other', + featureFlags: [{ name: 'test-2' }], + extensions: [], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText("Flags: test-1 from 'test', test-2 from 'other'"), + ).resolves.toBeInTheDocument(); + }); + + it('should make the app structure available through the AppTreeApi', async () => { + let appTreeApi: AppTreeApi | undefined = undefined; + + const app = createApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createFrontendPlugin({ + id: 'my-plugin', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () => { + const Component = () => { + appTreeApi = useApi(appTreeApiRef); + return
My Plugin Page
; + }; + return ; + }, + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + expect(appTreeApi).toBeDefined(); + const { tree } = appTreeApi!.getTree(); + + expect(String(tree.root)).toMatchInlineSnapshot(` + " + apis [ + + + + + + + + + + + + + + + + + + + + + themes [ + + + ] + + + components [ + + + + ] + + + + + ] + app [ + + root [ + + children [ + + nav [ + + ] + content [ + + routes [ + + ] + + ] + + ] + elements [ + + + ] + + ] + + ] + " + `); + }); + + it('should use "Loading..." as the default suspense fallback', async () => { + const app = createApp({ + configLoader: () => new Promise(() => {}), + }); + + await renderWithEffects(app.createRoot()); + + await expect(screen.findByText('Loading...')).resolves.toBeInTheDocument(); + }); + + it('should use no suspense fallback if the "loadingComponent" is null', async () => { + const app = createApp({ + configLoader: () => new Promise(() => {}), + loadingComponent: null, + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Loading...')).toBeNull(); + }); + + it('should use a custom "loadingComponent"', async () => { + const app = createApp({ + configLoader: () => new Promise(() => {}), + loadingComponent: "Custom loading message", + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom loading message')).toBeNull(); + }); + + it('should allow overriding the app plugin', async () => { + const app = createApp({ + configLoader: () => new Promise(() => {}), + features: [ + appPlugin.withOverrides({ + extensions: [ + appPlugin.getExtension('app/root').override({ + factory: () => [ + coreExtensionData.reactElement( +
Custom app root element
, + ), + ], + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom app root element')).toBeNull(); + }); + + describe('modules', () => { + it('should be able to override extensions with a plugin extension override', async () => { + const mod = createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('app/root').override({ + factory: () => [ + coreExtensionData.reactElement( +
Custom app root element
, + ), + ], + }), + ], + }); + + const app = createApp({ + configLoader: () => new Promise(() => {}), + features: [mod], + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom app root element')).toBeNull(); + }); + + it('should be able to override extensions with a standalone extension override', async () => { + const mod = createFrontendModule({ + pluginId: 'app', + extensions: [ + createExtension({ + name: 'root', + attachTo: { id: 'app', input: 'root' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement( +
Custom app root element
, + ), + ], + }), + ], + }); + + const app = createApp({ + configLoader: () => new Promise(() => {}), + features: [mod], + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom app root element')).toBeNull(); + }); + }); +}); diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx new file mode 100644 index 0000000000..79c9eb4a16 --- /dev/null +++ b/packages/frontend-defaults/src/createApp.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 React, { JSX, ReactNode } from 'react'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { stringifyError } from '@backstage/errors'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs'; +import { getAvailableFeatures } from './discovery'; +import { ConfigReader } from '@backstage/config'; +import appPlugin from '@backstage/plugin-app'; +import { + CreateAppRouteBinder, + FrontendFeature, + createSpecializedApp, +} from '@backstage/frontend-app-api'; + +/** + * A source of dynamically loaded frontend features. + * + * @public + */ +export interface CreateAppFeatureLoader { + /** + * Returns name of this loader. suitable for showing to users. + */ + getLoaderName(): string; + + /** + * Loads a number of features dynamically. + */ + load(options: { config: ConfigApi }): Promise<{ + features: FrontendFeature[]; + }>; +} + +/** + * Options for {@link createApp}. + * + * @public + */ +export interface CreateAppOptions { + features?: (FrontendFeature | CreateAppFeatureLoader)[]; + configLoader?: () => Promise<{ config: ConfigApi }>; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; + /** + * The component to render while loading the app (waiting for config, features, etc) + * + * Is the text "Loading..." by default. + * If set to "null" then no loading fallback component is rendered. * + */ + loadingComponent?: ReactNode; +} + +/** + * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index + * + * @public + */ +export function createApp(options?: CreateAppOptions): { + createRoot(): JSX.Element; +} { + let suspenseFallback = options?.loadingComponent; + if (suspenseFallback === undefined) { + suspenseFallback = 'Loading...'; + } + + async function appLoader() { + const config = + (await options?.configLoader?.().then(c => c.config)) ?? + ConfigReader.fromConfigs( + overrideBaseUrlConfigs(defaultConfigLoaderSync()), + ); + + const discoveredFeatures = getAvailableFeatures(config); + + const providedFeatures: FrontendFeature[] = []; + for (const entry of options?.features ?? []) { + if ('load' in entry) { + try { + const result = await entry.load({ config }); + providedFeatures.push(...result.features); + } catch (e) { + throw new Error( + `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError( + e, + )}`, + ); + } + } else { + providedFeatures.push(entry); + } + } + + const app = createSpecializedApp({ + config, + features: [appPlugin, ...discoveredFeatures, ...providedFeatures], + bindRoutes: options?.bindRoutes, + }).createRoot(); + + return { default: () => app }; + } + + return { + createRoot() { + const LazyApp = React.lazy(appLoader); + return ( + + + + ); + }, + }; +} diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx new file mode 100644 index 0000000000..ac1adf9b0f --- /dev/null +++ b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + IdentityApi, + SignInPageBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { render, screen, waitFor } from '@testing-library/react'; +import React, { useEffect } from 'react'; +import { createPublicSignInApp } from './createPublicSignInApp'; +import { MockConfigApi } from '@backstage/test-utils'; + +describe('createPublicSignInApp', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render a sign-in page', async () => { + const app = createPublicSignInApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: async () => () =>
Sign in page
, + }, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + await expect( + screen.findByText('Sign in page'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the form redirect on sign-in', async () => { + const submitSpy = jest + .spyOn(HTMLFormElement.prototype, 'submit') + .mockReturnValue(); + + const app = createPublicSignInApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: + async () => + ({ onSignInSuccess }) => { + useEffect(() => { + onSignInSuccess({ + getCredentials: async () => ({ token: 'mock-token' }), + } as IdentityApi); + }, [onSignInSuccess]); + return
; + }, + }, + }), + ], + }), + ], + }); + + const { baseElement } = render(app.createRoot()); + + await waitFor(() => { + expect(submitSpy).toHaveBeenCalled(); + }); + + expect(baseElement).toMatchInlineSnapshot(` + +
+
+ + + + +
+ + `); + }); +}); diff --git a/packages/frontend-defaults/src/createPublicSignInApp.tsx b/packages/frontend-defaults/src/createPublicSignInApp.tsx new file mode 100644 index 0000000000..a8fd456c73 --- /dev/null +++ b/packages/frontend-defaults/src/createPublicSignInApp.tsx @@ -0,0 +1,108 @@ +/* + * 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, + createFrontendModule, + identityApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; +import React from 'react'; +import { useAsync, useMountEffect } from '@react-hookz/web'; +import { CreateAppOptions, createApp } from './createApp'; +import appPlugin from '@backstage/plugin-app'; + +// This is a copy of the CookieAuthRedirect component from the auth-react +// plugin, to avoid a dependency on that package. Long-term we want this to be +// the only implementation and remove the one in auth-react once the old frontend system is gone. + +// TODO(Rugvip): Should this be part of the app plugin instead? since it owns the backend part of it. + +/** @internal */ +export function InternalCookieAuthRedirect() { + const identityApi = useApi(identityApiRef); + + const [state, actions] = useAsync(async () => { + const { token } = await identityApi.getCredentials(); + if (!token) { + throw new Error('Expected Backstage token in sign-in response'); + } + return token; + }); + + useMountEffect(actions.execute); + + if (state.status === 'error' && state.error) { + return <>An error occurred: {state.error.message}; + } + + if (state.status === 'success' && state.result) { + return ( +
form?.submit()} + action={window.location.href} + method="POST" + style={{ visibility: 'hidden' }} + > + + + + + ); + } + + return null; +} + +/** + * Creates an app that is suitable for the public sign-in page, for use in the `index-public-experimental.tsx` file. + * + * @remarks + * + * This app has an override for the `app/layout` extension, which means that + * most extension typically installed in an app will be ignored. However, you + * can still for example install API and root element extensions. + * + * A typical setup of this app will only install a custom sign-in page. + * + * @example + * ```ts + * const app = createPublicSignInApp({ + * features: [signInPageModule], + * }); + * ``` + * + * @public + */ +export function createPublicSignInApp(options?: CreateAppOptions) { + return createApp({ + ...options, + features: [ + ...(options?.features ?? []), + // This is a rather than app plugin override in order for it to take precedence over any supplied app plugin override + createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('app/layout').override({ + factory: () => [ + coreExtensionData.reactElement(), + ], + }), + ], + }), + ], + }); +} diff --git a/packages/frontend-app-api/src/wiring/discovery.test.ts b/packages/frontend-defaults/src/discovery.test.ts similarity index 89% rename from packages/frontend-app-api/src/wiring/discovery.test.ts rename to packages/frontend-defaults/src/discovery.test.ts index 6008ce6746..8570ca1bc7 100644 --- a/packages/frontend-app-api/src/wiring/discovery.test.ts +++ b/packages/frontend-defaults/src/discovery.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createPlugin } from '@backstage/frontend-plugin-api'; +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { getAvailableFeatures } from './discovery'; import { ConfigReader } from '@backstage/config'; @@ -42,7 +42,7 @@ describe('getAvailableFeatures', () => { }); it('should discover a plugin', () => { - const testPlugin = createPlugin({ id: 'test' }); + const testPlugin = createFrontendPlugin({ id: 'test' }); globalSpy.mockReturnValue({ modules: [{ default: testPlugin }], }); @@ -67,9 +67,9 @@ describe('getAvailableFeatures', () => { }); it('should discover multiple plugins', () => { - const test1Plugin = createPlugin({ id: 'test1' }); - const test2Plugin = createPlugin({ id: 'test2' }); - const test3Plugin = createPlugin({ id: 'test3' }); + const test1Plugin = createFrontendPlugin({ id: 'test1' }); + const test2Plugin = createFrontendPlugin({ id: 'test2' }); + const test3Plugin = createFrontendPlugin({ id: 'test3' }); globalSpy.mockReturnValue({ modules: [ { default: test1Plugin }, diff --git a/packages/frontend-app-api/src/wiring/discovery.ts b/packages/frontend-defaults/src/discovery.ts similarity index 89% rename from packages/frontend-app-api/src/wiring/discovery.ts rename to packages/frontend-defaults/src/discovery.ts index 4e50290350..e04960dc48 100644 --- a/packages/frontend-app-api/src/wiring/discovery.ts +++ b/packages/frontend-defaults/src/discovery.ts @@ -15,7 +15,7 @@ */ import { Config, ConfigReader } from '@backstage/config'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-app-api'; interface DiscoveryGlobal { modules: Array<{ name: string; export?: string; default: unknown }>; @@ -53,7 +53,7 @@ function readPackageDetectionConfig(config: Config) { } /** - * @public + * @internal */ export function getAvailableFeatures(config: Config): FrontendFeature[] { const discovered = ( @@ -84,6 +84,10 @@ export function getAvailableFeatures(config: Config): FrontendFeature[] { function isBackstageFeature(obj: unknown): obj is FrontendFeature { if (obj !== null && typeof obj === 'object' && '$$type' in obj) { return ( + obj.$$type === '@backstage/FrontendPlugin' || + obj.$$type === '@backstage/FrontendModule' || + // TODO: Remove this once the old plugin type and extension overrides + // are no longer supported obj.$$type === '@backstage/BackstagePlugin' || obj.$$type === '@backstage/ExtensionOverrides' ); diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts new file mode 100644 index 0000000000..7ee60a2116 --- /dev/null +++ b/packages/frontend-defaults/src/index.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/** + * APIs for creating Backstage apps with a default setup. + * + * @packageDocumentation + */ + +export { + createApp, + type CreateAppOptions, + type CreateAppFeatureLoader, +} from './createApp'; +export { createPublicSignInApp } from './createPublicSignInApp'; diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts b/packages/frontend-defaults/src/setupTests.ts similarity index 90% rename from packages/backend-app-api/src/services/implementations/httpAuth/index.ts rename to packages/frontend-defaults/src/setupTests.ts index edd7e53026..91af6695ac 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts +++ b/packages/frontend-defaults/src/setupTests.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { httpAuthServiceFactory } from './httpAuthServiceFactory'; +import '@testing-library/jest-dom'; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 302d541b18..e16c08ac7a 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,355 @@ # @backstage/frontend-plugin-api +## 0.8.0-next.1 + +### Patch Changes + +- c816e2d: Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + + Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 0.8.0-next.0 + +### Minor Changes + +- 5446061: **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. +- fec8b57: **BREAKING**: Updated the type parameters for `ExtensionDefinition` and `ExtensionBlueprint` to only have a single object parameter. The base type parameter is exported as `ExtensionDefinitionParameters` and `ExtensionBlueprintParameters` respectively. This is shipped as an immediate breaking change as we expect usage of these types to be rare, and it does not affect the runtime behavior of the API. + + This is a breaking change as it changes the type parameters. Existing usage can generally be updated as follows: + + - `ExtensionDefinition` -> `ExtensionDefinition` + - `ExtensionDefinition` -> `ExtensionDefinition` + - `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig }>` + - `ExtensionDefinition` -> `ExtensionDefinition<{ config: TConfig, configInput: TConfigInput }>` + + If you need to infer the parameter you can use `ExtensionDefinitionParameters`, for example: + + ```ts + import { + ExtensionDefinition, + ExtensionDefinitionParameters, + } from '@backstage/frontend-plugin-api'; + + function myUtility( + ext: ExtensionDefinition, + ): T['config'] { + // ... + } + ``` + + The same patterns apply to `ExtensionBlueprint`. + + This change is made to improve the readability of API references and ability to evolve the type parameters in the future. + +### Patch Changes + +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. +- f3a2b91: Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension. +- 98850de: Added support for defining `replaces` in `createExtensionInput` which will allow extensions to redirect missing `attachTo` points to an input of the created extension. + + ```ts + export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + // attachTo: { id: 'app', input: 'themes'} will be redirected to this input instead + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: () { + ... + } + }); + ``` + +- 4a66456: A new `apis` parameter has been added to `factory` for extensions. This is a way to access utility APIs without being coupled to the React context. +- 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 + +## 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 + +## 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 + ## 0.7.0-next.2 ### Minor Changes diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index f39add924a..75d91d28f0 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -89,8 +89,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; -import { ZodSchema } from 'zod'; -import { ZodTypeDef } from 'zod'; export { AlertApi }; @@ -147,11 +145,6 @@ export { AnyApiFactory }; export { AnyApiRef }; -// @public @deprecated (undocumented) -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - // @public (undocumented) export type AnyExtensionDataRef = ExtensionDataRef< unknown, @@ -161,17 +154,6 @@ export type AnyExtensionDataRef = ExtensionDataRef< } >; -// @public @deprecated (undocumented) -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { - optional: boolean; - singleton: boolean; - } - >; -}; - // @public (undocumented) export type AnyExternalRoutes = { [name in string]: ExternalRouteRef; @@ -190,25 +172,25 @@ export type AnyRoutes = { }; // @public -export const ApiBlueprint: ExtensionBlueprint< - 'api', - undefined, - undefined, - { +export const ApiBlueprint: ExtensionBlueprint<{ + kind: 'api'; + namespace: undefined; + name: undefined; + params: { factory: AnyApiFactory; - }, - ConfigurableExtensionDataRef, - {}, - {}, - {}, - { + }; + output: ConfigurableExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { factory: ConfigurableExtensionDataRef< AnyApiFactory, 'core.api.factory', {} >; - } ->; + }; +}>; export { ApiFactory }; @@ -262,39 +244,39 @@ export interface AppNodeSpec { } // @public -export const AppRootElementBlueprint: ExtensionBlueprint< - 'app-root-element', - undefined, - undefined, - { +export const AppRootElementBlueprint: ExtensionBlueprint<{ + kind: 'app-root-element'; + namespace: undefined; + name: undefined; + params: { element: JSX.Element | (() => JSX.Element); - }, - ConfigurableExtensionDataRef, - {}, - {}, - {}, - never ->; + }; + output: ConfigurableExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: never; +}>; // @public -export const AppRootWrapperBlueprint: ExtensionBlueprint< - 'app-root-wrapper', - undefined, - undefined, - { +export const AppRootWrapperBlueprint: ExtensionBlueprint<{ + kind: 'app-root-wrapper'; + namespace: undefined; + name: undefined; + params: { Component: ComponentType>; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< React_2.ComponentType<{ children?: React_2.ReactNode; }>, 'app.root.wrapper', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { component: ConfigurableExtensionDataRef< React_2.ComponentType<{ children?: React_2.ReactNode; @@ -302,8 +284,8 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint< 'app.root.wrapper', {} >; - } ->; + }; +}>; export { AppTheme }; @@ -338,25 +320,14 @@ export { BackstageIdentityApi }; export { BackstageIdentityResponse }; -// @public (undocumented) -export interface BackstagePlugin< +// @public @deprecated (undocumented) +export type BackstagePlugin< TRoutes extends AnyRoutes = AnyRoutes, TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, TExtensionMap extends { - [id in string]: ExtensionDefinition; + [id in string]: ExtensionDefinition; } = {}, -> { - // (undocumented) - readonly $$type: '@backstage/BackstagePlugin'; - // (undocumented) - readonly externalRoutes: TExternalRoutes; - // (undocumented) - getExtension(id: TId): TExtensionMap[TId]; - // (undocumented) - readonly id: string; - // (undocumented) - readonly routes: TRoutes; -} +> = FrontendPlugin; export { BackstageUserIdentity }; @@ -403,7 +374,7 @@ export interface ConfigurableExtensionDataRef< optional(): ConfigurableExtensionDataRef< TData, TId, - TData & { + TConfig & { optional: true; } >; @@ -446,139 +417,46 @@ export type CoreNotFoundErrorPageProps = { // @public (undocumented) export type CoreProgressProps = {}; -// @public @deprecated (undocumented) -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; - -// @public (undocumented) -export namespace createApiExtension { - const // (undocumented) - factoryDataRef: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; -} - export { createApiFactory }; export { createApiRef }; -// @public @deprecated -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX_2.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX_2.Element); -}): ExtensionDefinition; - -// @public @deprecated -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - // @public (undocumented) -export namespace createAppRootWrapperExtension { - const // (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.root.wrapper', - {} - >; -} - -// @public (undocumented) -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; +}): ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + ref: ComponentRef; + impl: ComponentType; + }, + 'core.component.component', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'component'; + namespace: undefined; + name: string; +}>; // @public (undocumented) export namespace createComponentExtension { @@ -599,6 +477,55 @@ export function createComponentRef(options: { }): ComponentRef; // @public (undocumented) +export function createExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + TConfigSchema extends { + [key: string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, +>( + options: CreateExtensionOptions< + TKind, + undefined, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; + +// @public @deprecated (undocumented) export function createExtension< UOutput extends AnyExtensionDataRef, TInputs extends { @@ -627,40 +554,86 @@ export function createExtension< TConfigSchema, UFactoryOutput >, -): ExtensionDefinition< - { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >, - UOutput, - TInputs, - string | undefined extends TKind ? undefined : TKind, - string | undefined extends TNamespace ? undefined : TNamespace, - string | undefined extends TName ? undefined : TName ->; - -// @public @deprecated (undocumented) -export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; +): ExtensionDefinition<{ + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; // @public export function createExtensionBlueprint< - TParams, + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + TConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { + [name in string]: AnyExtensionDataRef; + } = never, +>( + options: CreateExtensionBlueprintOptions< + TKind, + undefined, + TName, + TParams, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: undefined; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + dataRefs: TDataRefs; +}>; + +// @public @deprecated (undocumented) +export function createExtensionBlueprint< + TParams extends object, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -693,27 +666,27 @@ export function createExtensionBlueprint< UFactoryOutput, TDataRefs >, -): ExtensionBlueprint< - TKind, - TNamespace, - TName, - TParams, - UOutput, - string extends keyof TInputs ? {} : TInputs, - string extends keyof TConfigSchema +): ExtensionBlueprint<{ + kind: TKind; + namespace: TNamespace; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema ? {} : { [key in keyof TConfigSchema]: z.infer>; - }, - string extends keyof TConfigSchema + }; + configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >, - TDataRefs ->; + >; + dataRefs: TDataRefs; +}>; // @public (undocumented) export type CreateExtensionBlueprintOptions< @@ -756,6 +729,7 @@ export type CreateExtensionBlueprintOptions< params: TParams, context: { node: AppNode; + apis: ApiHolder; config: { [key in keyof TConfigSchema]: z.infer>; }; @@ -777,24 +751,6 @@ export function createExtensionDataRef(): { }): ConfigurableExtensionDataRef; }; -// @public @deprecated (undocumented) -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton?: boolean; - optional?: boolean; - }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; - // @public (undocumented) export function createExtensionInput< UExtensionData extends ExtensionDataRef< @@ -810,7 +766,12 @@ export function createExtensionInput< }, >( extensionData: Array, - config?: TConfig, + config?: TConfig & { + replaces?: Array<{ + id: string; + input: string; + }>; + }, ): ExtensionInput< UExtensionData, { @@ -854,6 +815,7 @@ export type CreateExtensionOptions< }; factory(context: { node: AppNode; + apis: ApiHolder; config: { [key in keyof TConfigSchema]: z.infer>; }; @@ -861,7 +823,7 @@ export type CreateExtensionOptions< }): Iterable; } & VerifyExtensionFactoryOutput; -// @public (undocumented) +// @public @deprecated (undocumented) export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides; @@ -889,110 +851,34 @@ export function createExternalRouteRef< } >; -// @public @deprecated -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent_2; -}): ExtensionDefinition< - { - title: string; - }, - { - title?: string | undefined; - }, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; +// @public (undocumented) +export function createFrontendModule< + TId extends string, + TExtensions extends readonly ExtensionDefinition[] = [], +>(options: CreateFrontendModuleOptions): FrontendModule; // @public (undocumented) -export namespace createNavItemExtension { - const // (undocumented) - targetDataRef: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent_2; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; +export interface CreateFrontendModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + // (undocumented) + extensions?: TExtensions; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; + // (undocumented) + pluginId: TPluginId; } -// @public @deprecated -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; - // @public (undocumented) -export namespace createNavLogoExtension { - const // (undocumented) - logoElementsDataRef: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element | undefined; - logoFull?: JSX.Element | undefined; - }, - 'core.nav-logo.logo-elements', - {} - >; -} - -// @public @deprecated -export function createPageExtension< - TConfig extends { - path: string; - }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition; - -// @public (undocumented) -export function createPlugin< +export function createFrontendPlugin< TId extends string, TRoutes extends AnyRoutes = {}, TExternalRoutes extends AnyExternalRoutes = {}, - TExtensions extends readonly ExtensionDefinition[] = [], + TExtensions extends readonly ExtensionDefinition[] = [], >( options: PluginOptions, -): BackstagePlugin< +): FrontendPlugin< TRoutes, TExternalRoutes, { @@ -1003,6 +889,9 @@ export function createPlugin< } >; +// @public @deprecated (undocumented) +export const createPlugin: typeof createFrontendPlugin; + // @public export function createRouteRef< TParams extends @@ -1023,75 +912,6 @@ export function createRouteRef< } >; -// @public @deprecated -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition; - -// @public (undocumented) -export namespace createRouterExtension { - const // (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType<{ - children?: React_2.ReactNode; - }>, - 'app.router.wrapper', - {} - >; -} - -// @public @deprecated (undocumented) -export function createSchemaFromZod( - schemaCreator: (zImpl: typeof z) => ZodSchema, -): PortableSchema; - -// @public @deprecated (undocumented) -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition; - -// @public (undocumented) -export namespace createSignInPageExtension { - const // (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - React_2.ComponentType, - 'core.sign-in-page.component', - {} - >; -} - // @public export function createSubRouteRef< Path extends string, @@ -1101,60 +921,6 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public @deprecated (undocumented) -export function createThemeExtension( - theme: AppTheme, -): ExtensionDefinition< - unknown, - unknown, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; - -// @public @deprecated (undocumented) -export namespace createThemeExtension { - const // (undocumented) - themeDataRef: ConfigurableExtensionDataRef< - AppTheme, - 'core.theme.theme', - {} - >; -} - -// @public @deprecated (undocumented) -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition< - unknown, - unknown, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; - -// @public @deprecated (undocumented) -export namespace createTranslationExtension { - const // (undocumented) - translationDataRef: ConfigurableExtensionDataRef< - | TranslationResource - | TranslationMessages< - string, - { - [x: string]: string; - }, - boolean - >, - 'core.translation.translation', - {} - >; -} - export { createTranslationMessages }; export { createTranslationRef }; @@ -1192,33 +958,33 @@ export interface Extension { // @public (undocumented) export interface ExtensionBlueprint< - TKind extends string, - TNamespace extends string | undefined, - TName extends string | undefined, - TParams, - UOutput extends AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }, - TConfig extends { - [key in string]: unknown; - }, - TConfigInput extends { - [key in string]: unknown; - }, - TDataRefs extends { - [name in string]: AnyExtensionDataRef; - }, + T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters, > { // (undocumented) - dataRefs: TDataRefs; + dataRefs: T['dataRefs']; // (undocumented) + make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + >(args: { + namespace?: undefined; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; + // @deprecated (undocumented) make< TNewNamespace extends string | undefined, TNewName extends string | undefined, @@ -1230,16 +996,18 @@ export interface ExtensionBlueprint< input: string; }; disabled?: boolean; - params: TParams; - }): ExtensionDefinition< - TConfig, - TConfigInput, - UOutput, - TInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName - >; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: string | undefined extends TNewNamespace + ? T['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; makeWithOverrides< TNewNamespace extends string | undefined, TNewName extends string | undefined, @@ -1258,7 +1026,7 @@ export interface ExtensionBlueprint< >; }, >(args: { - namespace?: TNewNamespace; + namespace?: undefined; name?: TNewName; attachTo?: { id: string; @@ -1266,65 +1034,198 @@ export interface ExtensionBlueprint< }; disabled?: boolean; inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & + [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; config?: { schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & + [KName in keyof T['config']]?: `Error: Config key '${KName & string}' is already defined in parent schema`; }; }; factory( originalFactory: ( - params: TParams, + params: T['params'], context?: { - config?: TConfig; - inputs?: Expand>; + config?: T['config']; + inputs?: ResolveInputValueOverrides>; }, - ) => ExtensionDataContainer, + ) => ExtensionDataContainer>, context: { node: AppNode; - config: TConfig & { + apis: ApiHolder; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, UFactoryOutput >; - }): ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + }): ExtensionDefinition<{ + config: (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }) & + T['config']; + configInput: (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; + // @deprecated (undocumented) + makeWithOverrides< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } >; - } & TConfig, - z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - }> - > & - TConfigInput, - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName - >; + }, + >(args: { + namespace: TNewNamespace; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: ( + params: T['params'], + context?: { + config?: T['config']; + inputs?: ResolveInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): ExtensionDefinition<{ + config: (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }) & + T['config']; + configInput: (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: string | undefined extends TNewNamespace + ? T['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; } +// @public (undocumented) +export type ExtensionBlueprintParameters = { + kind: string; + namespace?: string; + name?: string; + params?: object; + configInput?: { + [K in string]: any; + }; + config?: { + [K in string]: any; + }; + output?: AnyExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + dataRefs?: { + [name in string]: AnyExtensionDataRef; + }; +}; + // @public (undocumented) export function ExtensionBoundary( props: ExtensionBoundaryProps, ): React_2.JSX.Element; +// @public (undocumented) +export namespace ExtensionBoundary { + // (undocumented) + export function lazy( + appNode: AppNode, + lazyElement: () => Promise, + ): JSX.Element; +} + // @public (undocumented) export interface ExtensionBoundaryProps { // (undocumented) @@ -1383,57 +1284,12 @@ export type ExtensionDataValue = { readonly value: TData; }; -// @public @deprecated -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; - // @public (undocumented) -export interface ExtensionDefinition< - TConfig, - TConfigInput = TConfig, - UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, -> { - // (undocumented) +export type ExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> = { $$type: '@backstage/ExtensionDefinition'; - // (undocumented) - readonly attachTo: { - id: string; - input: string; - }; - // (undocumented) - readonly configSchema?: PortableSchema; - // (undocumented) - readonly disabled: boolean; - // (undocumented) - readonly kind?: TKind; - // (undocumented) - readonly name?: TName; - // (undocumented) - readonly namespace?: TNamespace; - // (undocumented) + readonly T: T; override< TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; @@ -1457,60 +1313,86 @@ export interface ExtensionDefinition< }; disabled?: boolean; inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & + [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; config?: { schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & + [KName in keyof T['config']]?: `Error: Config key '${KName & string}' is already defined in parent schema`; }; }; factory( originalFactory: (context?: { - config?: TConfig; - inputs?: Expand>; - }) => ExtensionDataContainer, + config?: T['config']; + inputs?: ResolveInputValueOverrides>; + }) => ExtensionDataContainer>, context: { node: AppNode; - config: TConfig & { + apis: ApiHolder; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable; } & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, UFactoryOutput >, - ): ExtensionDefinition< - { + ): ExtensionDefinition<{ + kind: T['kind']; + namespace: T['namespace']; + name: T['name']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; - } & TConfig, - z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - }> - > & - TConfigInput, - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs, - TKind, - TNamespace, - TName - >; -} + }; + configInput: T['configInput'] & + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >; + }>; +}; + +// @public (undocumented) +export type ExtensionDefinitionParameters = { + kind?: string; + namespace?: string; + name?: string; + configInput?: { + [K in string]: any; + }; + config?: { + [K in string]: any; + }; + output?: AnyExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; +}; // @public (undocumented) export interface ExtensionInput< - TExtensionData extends ExtensionDataRef< + UExtensionData extends ExtensionDataRef< unknown, string, { @@ -1527,7 +1409,12 @@ export interface ExtensionInput< // (undocumented) config: TConfig; // (undocumented) - extensionData: Array; + extensionData: Array; + // (undocumented) + replaces?: Array<{ + id: string; + input: string; + }>; } // @public (undocumented) @@ -1536,10 +1423,10 @@ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; } -// @public (undocumented) +// @public @deprecated (undocumented) export interface ExtensionOverridesOptions { // (undocumented) - extensions: ExtensionDefinition[]; + extensions: ExtensionDefinition[]; // (undocumented) featureFlags?: FeatureFlagConfig[]; } @@ -1573,8 +1460,40 @@ export { FetchApi }; export { fetchApiRef }; +// @public @deprecated (undocumented) +export type FrontendFeature = FrontendPlugin | ExtensionOverrides; + // @public (undocumented) -export type FrontendFeature = BackstagePlugin | ExtensionOverrides; +export interface FrontendModule { + // (undocumented) + readonly $$type: '@backstage/FrontendModule'; + // (undocumented) + readonly pluginId: string; +} + +// @public (undocumented) +export interface FrontendPlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { + [id in string]: ExtensionDefinition; + } = {}, +> { + // (undocumented) + readonly $$type: '@backstage/FrontendPlugin'; + // (undocumented) + readonly externalRoutes: TExternalRoutes; + // (undocumented) + getExtension(id: TId): TExtensionMap[TId]; + // (undocumented) + readonly id: string; + // (undocumented) + readonly routes: TRoutes; + // (undocumented) + withOverrides(options: { + extensions: Array; + }): FrontendPlugin; +} export { githubAuthApiRef }; @@ -1583,32 +1502,32 @@ export { gitlabAuthApiRef }; export { googleAuthApiRef }; // @public (undocumented) -export const IconBundleBlueprint: ExtensionBlueprint< - 'icon-bundle', - 'app', - undefined, - { +export const IconBundleBlueprint: ExtensionBlueprint<{ + kind: 'icon-bundle'; + namespace: undefined; + name: undefined; + params: { icons: { [x: string]: IconComponent; }; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< { [x: string]: IconComponent; }, 'core.icons', {} - >, - {}, - { + >; + inputs: {}; + config: { icons: string; test: string; - }, - { + }; + configInput: { test: string; icons?: string | undefined; - }, - { + }; + dataRefs: { icons: ConfigurableExtensionDataRef< { [x: string]: IconComponent; @@ -1616,8 +1535,8 @@ export const IconBundleBlueprint: ExtensionBlueprint< 'core.icons', {} >; - } ->; + }; +}>; // @public export type IconComponent = ComponentType< @@ -1644,69 +1563,19 @@ export { IdentityApi }; export { identityApiRef }; -// @public @deprecated (undocumented) -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - // (undocumented) - attachTo: { - id: string; - input: string; - }; - // (undocumented) - configSchema?: PortableSchema; - // (undocumented) - disabled?: boolean; - // (undocumented) - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; - // (undocumented) - inputs?: TInputs; - // (undocumented) - kind?: string; - // (undocumented) - name?: string; - // (undocumented) - namespace?: string; - // (undocumented) - output: TOutput; -} - -// @public @deprecated (undocumented) -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { - singleton: boolean; - optional: boolean; - }, -> { - // (undocumented) - $$type: '@backstage/ExtensionInput'; - // (undocumented) - config: TConfig; - // (undocumented) - extensionData: TExtensionDataMap; -} - export { microsoftAuthApiRef }; // @public -export const NavItemBlueprint: ExtensionBlueprint< - 'nav-item', - undefined, - undefined, - { +export const NavItemBlueprint: ExtensionBlueprint<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + params: { title: string; icon: IconComponent_2; routeRef: RouteRef; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< { title: string; icon: IconComponent_2; @@ -1714,11 +1583,11 @@ export const NavItemBlueprint: ExtensionBlueprint< }, 'core.nav-item.target', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { target: ConfigurableExtensionDataRef< { title: string; @@ -1728,30 +1597,30 @@ export const NavItemBlueprint: ExtensionBlueprint< 'core.nav-item.target', {} >; - } ->; + }; +}>; // @public -export const NavLogoBlueprint: ExtensionBlueprint< - 'nav-logo', - undefined, - undefined, - { +export const NavLogoBlueprint: ExtensionBlueprint<{ + kind: 'nav-logo'; + namespace: undefined; + name: undefined; + params: { logoIcon: JSX.Element; logoFull: JSX.Element; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< { logoIcon?: JSX.Element | undefined; logoFull?: JSX.Element | undefined; }, 'core.nav-logo.logo-elements', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { logoElements: ConfigurableExtensionDataRef< { logoIcon?: JSX.Element | undefined; @@ -1760,8 +1629,8 @@ export const NavLogoBlueprint: ExtensionBlueprint< 'core.nav-logo.logo-elements', {} >; - } ->; + }; +}>; export { OAuthApi }; @@ -1782,33 +1651,34 @@ export { oneloginAuthApiRef }; export { OpenIdConnectApi }; // @public -export const PageBlueprint: ExtensionBlueprint< - 'page', - undefined, - undefined, - { +export const PageBlueprint: ExtensionBlueprint<{ + kind: 'page'; + namespace: undefined; + name: undefined; + params: { defaultPath: string; loader: () => Promise; routeRef?: RouteRef | undefined; - }, - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< - RouteRef, - 'core.routing.ref', - RouteRef & { - optional: true; - } - >, - {}, - { + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + config: { path: string | undefined; - }, - { + }; + configInput: { path?: string | undefined; - }, - never ->; + }; + dataRefs: never; +}>; export { PendingOAuthRequest }; @@ -1817,7 +1687,7 @@ export interface PluginOptions< TId extends string, TRoutes extends AnyRoutes, TExternalRoutes extends AnyExternalRoutes, - TExtensions extends readonly ExtensionDefinition[], + TExtensions extends readonly ExtensionDefinition[], > { // (undocumented) extensions?: TExtensions; @@ -1848,17 +1718,12 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; // @public export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -1868,6 +1733,73 @@ export type ResolvedExtensionInputs< : Expand | undefined>; }; +// @public (undocumented) +export type ResolveInputValueOverrides< + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + } = { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, +> = Expand< + { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? never + : KName + : never]: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { + optional: boolean; + singleton: infer ISingleton extends boolean; + } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } & { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? KName + : never + : never]?: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { + optional: boolean; + singleton: infer ISingleton extends boolean; + } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } +>; + // @public export type RouteFunc = ( ...[params]: TParams extends undefined @@ -1876,24 +1808,24 @@ export type RouteFunc = ( ) => string; // @public (undocumented) -export const RouterBlueprint: ExtensionBlueprint< - 'app-router-component', - undefined, - undefined, - { +export const RouterBlueprint: ExtensionBlueprint<{ + kind: 'app-router-component'; + namespace: undefined; + name: undefined; + params: { Component: ComponentType>; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< ComponentType<{ children?: ReactNode; }>, 'app.router.wrapper', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { component: ConfigurableExtensionDataRef< ComponentType<{ children?: ReactNode; @@ -1901,8 +1833,8 @@ export const RouterBlueprint: ExtensionBlueprint< 'app.router.wrapper', {} >; - } ->; + }; +}>; // @public export interface RouteRef< @@ -1939,29 +1871,29 @@ export { SessionApi }; export { SessionState }; // @public -export const SignInPageBlueprint: ExtensionBlueprint< - 'sign-in-page', - undefined, - undefined, - { +export const SignInPageBlueprint: ExtensionBlueprint<{ + kind: 'sign-in-page'; + namespace: undefined; + name: undefined; + params: { loader: () => Promise>; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< React_2.ComponentType, 'core.sign-in-page.component', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { component: ConfigurableExtensionDataRef< React_2.ComponentType, 'core.sign-in-page.component', {} >; - } ->; + }; +}>; export { StorageApi }; @@ -1982,31 +1914,31 @@ export interface SubRouteRef< } // @public -export const ThemeBlueprint: ExtensionBlueprint< - 'theme', - 'app', - undefined, - { +export const ThemeBlueprint: ExtensionBlueprint<{ + kind: 'theme'; + namespace: undefined; + name: undefined; + params: { theme: AppTheme; - }, - ConfigurableExtensionDataRef, - {}, - {}, - {}, - { + }; + output: ConfigurableExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { theme: ConfigurableExtensionDataRef; - } ->; + }; +}>; // @public -export const TranslationBlueprint: ExtensionBlueprint< - 'translation', - undefined, - undefined, - { +export const TranslationBlueprint: ExtensionBlueprint<{ + kind: 'translation'; + namespace: undefined; + name: undefined; + params: { resource: TranslationResource | TranslationMessages; - }, - ConfigurableExtensionDataRef< + }; + output: ConfigurableExtensionDataRef< | TranslationResource | TranslationMessages< string, @@ -2017,11 +1949,11 @@ export const TranslationBlueprint: ExtensionBlueprint< >, 'core.translation.translation', {} - >, - {}, - {}, - {}, - { + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { translation: ConfigurableExtensionDataRef< | TranslationResource | TranslationMessages< @@ -2034,8 +1966,8 @@ export const TranslationBlueprint: ExtensionBlueprint< 'core.translation.translation', {} >; - } ->; + }; +}>; export { TranslationMessages }; diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 8f729a5c4d..55168aed65 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.7.0-next.2", + "version": "0.8.0-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 3b865a78bb..519b7a4d1a 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; @@ -30,14 +31,15 @@ describe('ApiBlueprint', () => { params: { factory, }, - namespace: 'test', + name: 'test', }); expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { - "id": "app", + "id": "root", "input": "apis", }, "configSchema": undefined, @@ -45,8 +47,8 @@ describe('ApiBlueprint', () => { "factory": [Function], "inputs": {}, "kind": "api", - "name": undefined, - "namespace": "test", + "name": "test", + "namespace": undefined, "output": [ [Function], ], @@ -70,7 +72,7 @@ describe('ApiBlueprint', () => { inputs: { test: createExtensionInput([ApiBlueprint.dataRefs.factory]), }, - namespace: api.id, + name: api.id, factory(originalFactory, { config: _config, inputs: _inputs }) { return originalFactory({ factory: createApiFactory({ @@ -85,8 +87,9 @@ describe('ApiBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { - "id": "app", + "id": "root", "input": "apis", }, "configSchema": { @@ -115,11 +118,12 @@ describe('ApiBlueprint', () => { "extensionData": [ [Function], ], + "replaces": undefined, }, }, "kind": "api", - "name": undefined, - "namespace": "test", + "name": "test", + "namespace": undefined, "output": [ [Function], ], diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index b5ba9e05f6..1f28a3cf54 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createApiExtension } from '../extensions/createApiExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { AnyApiFactory } from '@backstage/core-plugin-api'; +const factoryDataRef = createExtensionDataRef().with({ + id: 'core.api.factory', +}); + /** * Creates utility API extensions. * @@ -25,12 +28,12 @@ import { AnyApiFactory } from '@backstage/core-plugin-api'; */ export const ApiBlueprint = createExtensionBlueprint({ kind: 'api', - attachTo: { id: 'app', input: 'apis' }, - output: [createApiExtension.factoryDataRef], + attachTo: { id: 'root', input: 'apis' }, + output: [factoryDataRef], dataRefs: { - factory: createApiExtension.factoryDataRef, + factory: factoryDataRef, }, *factory(params: { factory: AnyApiFactory }) { - yield createApiExtension.factoryDataRef(params.factory); + yield factoryDataRef(params.factory); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx index 3fb8d7b37a..0f4d0b2092 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { AppRootElementBlueprint } from './AppRootElementBlueprint'; @@ -26,6 +27,7 @@ describe('AppRootElementBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/root", "input": "elements", diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts index bc6304923a..c823fa56cd 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { coreExtensionData, createExtensionBlueprint } from '../wiring'; /** diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx index f7feab1d46..f9214121ce 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { PageBlueprint } from './PageBlueprint'; -import { waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { coreExtensionData, createExtension, createExtensionInput, } from '../wiring'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; describe('AppRootWrapperBlueprint', () => { it('should return an extension with sensible defaults', () => { @@ -35,6 +35,7 @@ describe('AppRootWrapperBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/root", "input": "wrappers", @@ -58,29 +59,19 @@ describe('AppRootWrapperBlueprint', () => { it('should render the simple component wrapper', async () => { const extension = AppRootWrapperBlueprint.make({ + name: 'test', params: { Component: () =>
Hello
, }, }); - const { getByText } = createExtensionTester( - PageBlueprint.make({ - params: { - defaultPath: '/', - loader: async () =>
, - }, - }), - ) - .add(extension) - .render(); + renderInTestApp(
, { extensions: [extension] }); - await waitFor(() => expect(getByText('Hello')).toBeInTheDocument()); + await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument()); }); it('should render the complex component wrapper', async () => { const extension = AppRootWrapperBlueprint.makeWithOverrides({ - namespace: 'ns', - name: 'test', config: { schema: { name: z => z.string(), @@ -103,28 +94,30 @@ describe('AppRootWrapperBlueprint', () => { }, }); - const { getByText, getByTestId } = createExtensionTester( - PageBlueprint.make({ - params: { - defaultPath: '/', - loader: async () =>
Hi
, - }, - }), - ) - .add(extension, { config: { name: 'Robin' } }) - .add( + renderInTestApp(
, { + extensions: [ + extension, createExtension({ - attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, + name: 'test-child', + attachTo: { id: 'app-root-wrapper:test', input: 'children' }, output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
Its Me
)], }), - ) - .render(); + ], + config: { + app: { + extensions: [ + { + 'app-root-wrapper:test': { config: { name: 'Robin' } }, + }, + ], + }, + }, + }); await waitFor(() => { - expect(getByText('Hi')).toBeInTheDocument(); - expect(getByTestId('Robin-1')).toBeInTheDocument(); - expect(getByText('Its Me')).toBeInTheDocument(); + expect(screen.getByTestId('Robin-1')).toBeInTheDocument(); + expect(screen.getByText('Its Me')).toBeInTheDocument(); }); }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index c54686f54f..da11bd8cb0 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -16,8 +16,11 @@ import React from 'react'; import { ComponentType, PropsWithChildren } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createAppRootWrapperExtension } from '../extensions/createAppRootWrapperExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const componentDataRef = createExtensionDataRef< + ComponentType> +>().with({ id: 'app.root.wrapper' }); /** * Creates a extensions that render a React wrapper at the app root, enclosing @@ -29,9 +32,9 @@ import { createAppRootWrapperExtension } from '../extensions/createAppRootWrappe export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', attachTo: { id: 'app/root', input: 'wrappers' }, - output: [createAppRootWrapperExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createAppRootWrapperExtension.componentDataRef, + component: componentDataRef, }, *factory(params: { Component: ComponentType> }) { // todo(blam): not sure that this wrapping is even necessary anymore. @@ -39,6 +42,6 @@ export const AppRootWrapperBlueprint = createExtensionBlueprint({ return {props.children}; }; - yield createAppRootWrapperExtension.componentDataRef(Component); + yield componentDataRef(Component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts index deeabdb44c..51b3b6d633 100644 --- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { IconComponent } from '../icons'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; @@ -23,8 +24,7 @@ const iconsDataRef = createExtensionDataRef<{ /** @public */ export const IconBundleBlueprint = createExtensionBlueprint({ kind: 'icon-bundle', - namespace: 'app', - attachTo: { id: 'app', input: 'icons' }, + attachTo: { id: 'api:app/icons', input: 'icons' }, output: [iconsDataRef], config: { schema: { diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx index 94b9e7b8e6..ddde2ff47d 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx @@ -33,6 +33,7 @@ describe('NavItemBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/nav", "input": "items", @@ -77,7 +78,7 @@ describe('NavItemBlueprint', () => { const tester = createExtensionTester(extension); - expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ title: 'TEST', icon: MockIcon, routeRef: mockRouteRef, @@ -97,7 +98,7 @@ describe('NavItemBlueprint', () => { config: { title: 'OVERRIDDEN' }, }); - expect(tester.data(NavItemBlueprint.dataRefs.target)).toEqual({ + expect(tester.get(NavItemBlueprint.dataRefs.target)).toEqual({ title: 'OVERRIDDEN', icon: MockIcon, routeRef: mockRouteRef, diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 20f57421c7..197f72da73 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -16,8 +16,14 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { RouteRef } from '../routing'; -import { createExtensionBlueprint } from '../wiring'; -import { createNavItemExtension } from '../extensions/createNavItemExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +// TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef +const targetDataRef = createExtensionDataRef<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; +}>().with({ id: 'core.nav-item.target' }); /** * Creates extensions that make up the items of the nav bar. @@ -27,9 +33,9 @@ import { createNavItemExtension } from '../extensions/createNavItemExtension'; export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', attachTo: { id: 'app/nav', input: 'items' }, - output: [createNavItemExtension.targetDataRef], + output: [targetDataRef], dataRefs: { - target: createNavItemExtension.targetDataRef, + target: targetDataRef, }, factory: ( { @@ -43,7 +49,7 @@ export const NavItemBlueprint = createExtensionBlueprint({ }, { config }, ) => [ - createNavItemExtension.targetDataRef({ + targetDataRef({ title: config.title ?? title, icon, routeRef, diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx index 4a96475cf5..a08dda1201 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { NavLogoBlueprint } from './NavLogoBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; @@ -29,6 +30,7 @@ describe('NavLogoBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/nav", "input": "logos", @@ -64,7 +66,7 @@ describe('NavLogoBlueprint', () => { const tester = createExtensionTester(extension); - expect(tester.data(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ + expect(tester.get(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ logoFull, logoIcon, }); diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts index 8066646ca3..4a37859975 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const logoElementsDataRef = createExtensionDataRef<{ + logoIcon?: JSX.Element; + logoFull?: JSX.Element; +}>().with({ id: 'core.nav-logo.logo-elements' }); /** * Creates an extension that replaces the logo in the nav bar with your own. @@ -25,9 +29,9 @@ import { createNavLogoExtension } from '../extensions/createNavLogoExtension'; export const NavLogoBlueprint = createExtensionBlueprint({ kind: 'nav-logo', attachTo: { id: 'app/nav', input: 'logos' }, - output: [createNavLogoExtension.logoElementsDataRef], + output: [logoElementsDataRef], dataRefs: { - logoElements: createNavLogoExtension.logoElementsDataRef, + logoElements: logoElementsDataRef, }, *factory({ logoIcon, @@ -36,7 +40,7 @@ export const NavLogoBlueprint = createExtensionBlueprint({ logoIcon: JSX.Element; logoFull: JSX.Element; }) { - yield createNavLogoExtension.logoElementsDataRef({ + yield logoElementsDataRef({ logoIcon, logoFull, }); diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index c4bcb4b467..c586ed8c04 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -16,7 +16,10 @@ import React from 'react'; import { createRouteRef } from '../routing'; import { PageBlueprint } from './PageBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { coreExtensionData, createExtensionBlueprint, @@ -40,6 +43,7 @@ describe('PageBlueprint', () => { expect(myPage).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/routes", "input": "routes", @@ -98,9 +102,9 @@ describe('PageBlueprint', () => { // TODO(blam): test for the routePath output doesn't work, due to the the way the test harness works // expect(tester.data(coreExtensionData.routePath)).toBe('/test'); - expect(tester.data(coreExtensionData.routeRef)).toBe(mockRouteRef); + expect(tester.get(coreExtensionData.routeRef)).toBe(mockRouteRef); - const { getByTestId } = tester.render(); + const { getByTestId } = renderInTestApp(tester.reactElement()); await waitFor(() => expect(getByTestId('test')).toBeInTheDocument()); }); @@ -144,7 +148,7 @@ describe('PageBlueprint', () => { CardBlueprint.make({ name: 'card', params: {} }), ); - const { getByTestId, getByText } = tester.render(); + const { getByTestId, getByText } = renderInTestApp(tester.reactElement()); await waitFor(() => expect(getByTestId('card')).toBeInTheDocument()); await waitFor(() => diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index b224ea491e..303e99daaa 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { lazy } from 'react'; + import { RouteRef } from '../routing'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; import { ExtensionBoundary } from '../components'; @@ -48,17 +48,8 @@ export const PageBlueprint = createExtensionBlueprint({ }, { config, node }, ) { - const ExtensionComponent = lazy(() => - loader().then(element => ({ default: () => element })), - ); - yield coreExtensionData.routePath(config.path ?? defaultPath); - yield coreExtensionData.reactElement( - - - , - ); - + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); if (routeRef) { yield coreExtensionData.routeRef(routeRef); } diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx index fe40aaa8d7..ab6bb1eb1f 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx @@ -17,15 +17,12 @@ import React from 'react'; import { RouterBlueprint } from './RouterBlueprint'; import { MemoryRouter } from 'react-router-dom'; import { render, waitFor } from '@testing-library/react'; -import { createSpecializedApp } from '@backstage/frontend-app-api'; import { coreExtensionData, createExtension, createExtensionInput, - createExtensionOverrides, } from '../wiring'; -import { MockConfigApi } from '@backstage/test-utils'; -import { PageBlueprint } from './PageBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; describe('RouterBlueprint', () => { it('should return an extension when calling make with sensible defaults', () => { @@ -38,6 +35,7 @@ describe('RouterBlueprint', () => { expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/root", "input": "router", @@ -61,7 +59,6 @@ describe('RouterBlueprint', () => { it('should work with simple options', async () => { const extension = RouterBlueprint.make({ - namespace: 'test', params: { Component: ({ children }) => ( @@ -71,24 +68,14 @@ describe('RouterBlueprint', () => { }, }); - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - PageBlueprint.make({ - namespace: 'test', - params: { - defaultPath: '/', - loader: async () =>
, - }, - }), - ], - }), - ], - }); + const tester = createExtensionTester(extension); + const Component = tester.get(RouterBlueprint.dataRefs.component); - const { getByTestId } = render(app.createRoot()); + const { getByTestId } = render( + +
+ , + ); await waitFor(() => { expect(getByTestId('test-contents')).toBeInTheDocument(); @@ -98,7 +85,6 @@ describe('RouterBlueprint', () => { it('should work with complex options and props', async () => { const extension = RouterBlueprint.makeWithOverrides({ - namespace: 'test', name: 'test', config: { schema: { @@ -123,44 +109,27 @@ describe('RouterBlueprint', () => { }, }); - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createExtension({ - namespace: 'test', - attachTo: { - id: 'app-router-component:test/test', - input: 'children', - }, - output: [coreExtensionData.reactElement], - *factory() { - yield coreExtensionData.reactElement(
); - }, - }), - PageBlueprint.make({ - namespace: 'test', - params: { - defaultPath: '/', - loader: async () =>
, - }, - }), - ], - }), - ], - config: new MockConfigApi({ - app: { - extensions: [ - { - 'app-router-component:test/test': { config: { name: 'Robin' } }, - }, - ], + const tester = createExtensionTester(extension, { + config: { name: 'Robin' }, + }).add( + createExtension({ + attachTo: { + id: 'app-router-component:test', + input: 'children', + }, + output: [coreExtensionData.reactElement], + *factory() { + yield coreExtensionData.reactElement(
); }, }), - }); + ); + const Component = tester.get(RouterBlueprint.dataRefs.component); - const { getByTestId } = render(app.createRoot()); + const { getByTestId } = render( + +
+ , + ); await waitFor(() => { expect(getByTestId('test-contents')).toBeInTheDocument(); diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx index 24ee805659..503c9da0e8 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { ComponentType, PropsWithChildren } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createRouterExtension } from '../extensions/createRouterExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const componentDataRef = createExtensionDataRef< + ComponentType> +>().with({ id: 'app.router.wrapper' }); /** @public */ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, - output: [createRouterExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createRouterExtension.componentDataRef, + component: componentDataRef, }, *factory({ Component }: { Component: ComponentType> }) { - yield createRouterExtension.componentDataRef(Component); + yield componentDataRef(Component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index f97798a64c..a85b5e6f39 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -16,9 +16,11 @@ import React from 'react'; import { SignInPageBlueprint } from './SignInPageBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { screen, waitFor } from '@testing-library/react'; -import { coreExtensionData, createExtension } from '../wiring'; describe('SignInPageBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -29,6 +31,7 @@ describe('SignInPageBlueprint', () => { ).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { "id": "app/root", "input": "signInPage", @@ -54,26 +57,14 @@ describe('SignInPageBlueprint', () => { const MockSignInPage = () =>
; const extension = SignInPageBlueprint.make({ - name: 'test', params: { loader: async () => () => }, }); const tester = createExtensionTester(extension); - expect(tester.data(SignInPageBlueprint.dataRefs.component)).toBeDefined(); + const Component = tester.get(SignInPageBlueprint.dataRefs.component); - createExtensionTester( - createExtension({ - name: 'dummy', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element:
}), - }), - ) - .add(extension) - .render(); + renderInTestApp( {}} />); await waitFor(() => { expect(screen.getByTestId('mock-sign-in')).toBeInTheDocument(); diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index f87ae91452..561781c49f 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -13,12 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React, { ComponentType, lazy } from 'react'; -import { createExtensionBlueprint } from '../wiring'; -import { createSignInPageExtension } from '../extensions/createSignInPageExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { SignInPageProps } from '@backstage/core-plugin-api'; import { ExtensionBoundary } from '../components'; +const componentDataRef = createExtensionDataRef< + ComponentType +>().with({ id: 'core.sign-in-page.component' }); + /** * Creates an extension that replaces the sign in page. * @@ -27,9 +31,9 @@ import { ExtensionBoundary } from '../components'; export const SignInPageBlueprint = createExtensionBlueprint({ kind: 'sign-in-page', attachTo: { id: 'app/root', input: 'signInPage' }, - output: [createSignInPageExtension.componentDataRef], + output: [componentDataRef], dataRefs: { - component: createSignInPageExtension.componentDataRef, + component: componentDataRef, }, *factory( { @@ -43,7 +47,7 @@ export const SignInPageBlueprint = createExtensionBlueprint({ loader().then(component => ({ default: component })), ); - yield createSignInPageExtension.componentDataRef(props => ( + yield componentDataRef(props => ( diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 40323b6a31..89c2229fab 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -31,8 +31,9 @@ describe('ThemeBlueprint', () => { .toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { - "id": "app", + "id": "api:app/app-theme", "input": "themes", }, "configSchema": undefined, @@ -41,7 +42,7 @@ describe('ThemeBlueprint', () => { "inputs": {}, "kind": "theme", "name": "light", - "namespace": "app", + "namespace": undefined, "output": [ [Function], ], @@ -56,7 +57,7 @@ describe('ThemeBlueprint', () => { const extension = ThemeBlueprint.make({ params: { theme } }); expect( - createExtensionTester(extension).data(ThemeBlueprint.dataRefs.theme), + createExtensionTester(extension).get(ThemeBlueprint.dataRefs.theme), ).toEqual(theme); }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index 686c498c06..323078bc4e 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -15,8 +15,11 @@ */ import { AppTheme } from '@backstage/core-plugin-api'; -import { createExtensionBlueprint } from '../wiring'; -import { createThemeExtension } from '../extensions/createThemeExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const themeDataRef = createExtensionDataRef().with({ + id: 'core.theme.theme', +}); /** * Creates an extension that adds/replaces an app theme. @@ -25,13 +28,10 @@ import { createThemeExtension } from '../extensions/createThemeExtension'; */ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', - namespace: 'app', - attachTo: { id: 'app', input: 'themes' }, - output: [createThemeExtension.themeDataRef], + attachTo: { id: 'api:app/app-theme', input: 'themes' }, + output: [themeDataRef], dataRefs: { - theme: createThemeExtension.themeDataRef, + theme: themeDataRef, }, - factory: ({ theme }: { theme: AppTheme }) => [ - createThemeExtension.themeDataRef(theme), - ], + factory: ({ theme }: { theme: AppTheme }) => [themeDataRef(theme)], }); diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts index dfb918053a..6bccd68c53 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts @@ -46,8 +46,9 @@ describe('TranslationBlueprint', () => { ).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", + "T": undefined, "attachTo": { - "id": "app", + "id": "api:app/translations", "input": "translations", }, "configSchema": undefined, @@ -76,7 +77,7 @@ describe('TranslationBlueprint', () => { }); expect( - createExtensionTester(extension).data( + createExtensionTester(extension).get( TranslationBlueprint.dataRefs.translation, ), ).toBe(messages); diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts index bdb36e2191..dd4a9ddc09 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '../wiring'; -import { createTranslationExtension } from '../extensions/createTranslationExtension'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; import { TranslationMessages, TranslationResource } from '../translation'; +const translationDataRef = createExtensionDataRef< + TranslationResource | TranslationMessages +>().with({ id: 'core.translation.translation' }); + /** * Creates an extension that adds translations to your app. * @@ -25,14 +28,14 @@ import { TranslationMessages, TranslationResource } from '../translation'; */ export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', - attachTo: { id: 'app', input: 'translations' }, - output: [createTranslationExtension.translationDataRef], + attachTo: { id: 'api:app/translations', input: 'translations' }, + output: [translationDataRef], dataRefs: { - translation: createTranslationExtension.translationDataRef, + translation: translationDataRef, }, factory: ({ resource, }: { resource: TranslationResource | TranslationMessages; - }) => [createTranslationExtension.translationDataRef(resource)], + }) => [translationDataRef(resource)], }); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index 1f12f127e8..3e260f0c9b 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -23,31 +23,31 @@ import { } from '@backstage/test-utils'; import { ExtensionBoundary } from './ExtensionBoundary'; import { coreExtensionData, createExtension } from '../wiring'; -import { - analyticsApiRef, - createApiFactory, - useAnalytics, -} from '@backstage/core-plugin-api'; +import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; import { createRouteRef } from '../routing'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { createApiExtension } from '../extensions'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; const wrapInBoundaryExtension = (element?: JSX.Element) => { const routeRef = createRouteRef(); return createExtension({ name: 'test', attachTo: { id: 'app/routes', input: 'routes' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + ], factory({ node }) { - return { - routeRef, - path: '/', - element: {element}, - }; + return [ + coreExtensionData.reactElement( + {element}, + ), + coreExtensionData.routePath('/'), + coreExtensionData.routeRef(routeRef), + ]; }, }); }; @@ -58,7 +58,11 @@ describe('ExtensionBoundary', () => { const TextComponent = () => { return

{text}

; }; - createExtensionTester(wrapInBoundaryExtension()).render(); + renderInTestApp( + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + ); await waitFor(() => expect(screen.getByText(text)).toBeInTheDocument()); }); @@ -68,9 +72,11 @@ describe('ExtensionBoundary', () => { throw new Error(errorMsg); }; const { error } = await withLogCollector(['error'], async () => { - createExtensionTester( - wrapInBoundaryExtension(), - ).render(); + renderInTestApp( + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + ); await waitFor(() => expect(screen.getByText(errorMsg)).toBeInTheDocument(), ); @@ -97,13 +103,13 @@ describe('ExtensionBoundary', () => { return null; }; - createExtensionTester( - wrapInBoundaryExtension( - - - , - ), - ).render(); + renderInTestApp( + + {createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement()} + , + ); await waitFor(() => { const event = analyticsApiMock @@ -120,8 +126,9 @@ describe('ExtensionBoundary', () => { }); }); - // TODO(Rugvip): It's annoying to test the inverse of this currently, because the extension tester overrides the subject to always output a path - it('should emit analytics events if routable', async () => { + // TODO(Rugvip): Need a way to be able to override APIs in the app to be able to test this properly + // eslint-disable-next-line jest/no-disabled-tests + it.skip('should emit analytics events if routable', async () => { const Emitter = () => { const analytics = useAnalytics(); useEffect(() => { @@ -132,13 +139,12 @@ describe('ExtensionBoundary', () => { const analyticsApiMock = new MockAnalyticsApi(); await act(async () => { - createExtensionTester(wrapInBoundaryExtension()) - .add( - createApiExtension({ - factory: createApiFactory(analyticsApiRef, analyticsApiMock), - }), - ) - .render(); + renderInTestApp( + createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement(), + // { apis: [[analyticsApiRef, analyticsApiMock]] }, + ); }); expect(analyticsApiMock.getEvents()).toEqual([ diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index b6269484b0..176dfd4c3c 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -19,6 +19,7 @@ import React, { ReactNode, Suspense, useEffect, + lazy as reactLazy, } from 'react'; import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; import { ErrorBoundary } from './ErrorBoundary'; @@ -90,3 +91,20 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { ); } + +/** @public */ +export namespace ExtensionBoundary { + export function lazy( + appNode: AppNode, + lazyElement: () => Promise, + ): JSX.Element { + const ExtensionComponent = reactLazy(() => + lazyElement().then(element => ({ default: () => element })), + ); + return ( + + + + ); + } +} diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts deleted file mode 100644 index 8f8a0cd9ac..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.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. - */ - -import { createApiExtension } from './createApiExtension'; -import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; - -describe('createApiExtension', () => { - it('fills in the expected values for an existing factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = createApiFactory({ - api, - deps: {}, - factory: () => ({ foo: 'bar' }), - }); - - expect( - createApiExtension({ - factory, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('fills in the expected values for a ref and custom factory', () => { - const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = jest.fn(() => ({ foo: 'bar' })); - - const extension = createApiExtension({ - api, - inputs: {}, - factory({ config: _config, inputs: _inputs }) { - return createApiFactory({ - api, - deps: {}, - factory, - }); - }, - }); - // boo - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'api', - namespace: 'test', - attachTo: { id: 'app', input: 'apis' }, - disabled: false, - configSchema: undefined, - inputs: {}, - output: { - api: expect.objectContaining({ - $$type: '@backstage/ExtensionDataRef', - id: 'core.api.factory', - config: {}, - }), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts deleted file mode 100644 index b1db2a0106..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ /dev/null @@ -1,81 +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 { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; -import { PortableSchema } from '../schema'; -import { - ResolvedExtensionInputs, - createExtension, - createExtensionDataRef, -} from '../wiring'; -import { AnyExtensionInputMap } from '../wiring/createExtension'; -import { Expand } from '../types'; - -/** - * @public - * @deprecated Use {@link ApiBlueprint} instead. - */ -export function createApiExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - api: AnyApiRef; - factory: (options: { - config: TConfig; - inputs: Expand>; - }) => AnyApiFactory; - } - | { - factory: AnyApiFactory; - } - ) & { - configSchema?: PortableSchema; - inputs?: TInputs; - }, -) { - const { factory, configSchema, inputs: extensionInputs } = options; - - const apiRef = - 'api' in options ? options.api : (factory as { api: AnyApiRef }).api; - - return createExtension({ - kind: 'api', - // Since ApiRef IDs use a global namespace we use the namespace here in order to override - // potential plugin IDs and always end up with the format `api:` - namespace: apiRef.id, - attachTo: { id: 'app', input: 'apis' }, - inputs: extensionInputs, - configSchema, - output: { - api: createApiExtension.factoryDataRef, - }, - factory({ config, inputs }) { - if (typeof factory === 'function') { - return { api: factory({ config, inputs }) }; - } - return { api: factory }; - }, - }); -} - -/** @public */ -export namespace createApiExtension { - export const factoryDataRef = createExtensionDataRef().with({ - id: 'core.api.factory', - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx deleted file mode 100644 index 6e80f6fe13..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx +++ /dev/null @@ -1,109 +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 { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootElementExtension } from './createAppRootElementExtension'; - -describe('createAppRootElementExtension', () => { - it('works with simple options and just an element', async () => { - const extension = createAppRootElementExtension({ - element:
Hello
, - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - attachTo: { id: 'app/root', input: 'elements' }, - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension).render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and a callback', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootElementExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - attachTo: { id: 'other', input: 'slot' }, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - element: ({ inputs, config }) => ( -
- Hello, {config.name}, {inputs.children.length} -
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-element', - namespace: 'ns', - name: 'test', - attachTo: { id: 'other', input: 'slot' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-element:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
}), - }), - ) - .render(); - - await expect( - screen.findByText('Hello, Robin, 1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts deleted file mode 100644 index c8f7bdc366..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts +++ /dev/null @@ -1,72 +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 { JSX } from 'react'; -import { PortableSchema } from '../schema/types'; -import { Expand } from '../types'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; - -/** - * Creates an extension that renders a React element at the app root, outside of - * the app layout. This is useful for example for shared popups and similar. - * - * @public - * @deprecated Use {@link AppRootElementBlueprint} instead. - */ -export function createAppRootElementExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - element: - | JSX.Element - | ((options: { - inputs: Expand>; - config: TConfig; - }) => JSX.Element); -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-element', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'elements' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs, config }) { - return { - element: - typeof options.element === 'function' - ? options.element({ inputs, config }) - : options.element, - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx deleted file mode 100644 index 993c4fdee3..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx +++ /dev/null @@ -1,121 +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 { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -import { createPageExtension } from './createPageExtension'; - -describe('createAppRootWrapperExtension', () => { - it('works with simple options and no props', async () => { - const extension = createAppRootWrapperExtension({ - Component: () =>
Hello
, - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - attachTo: { id: 'app/root', input: 'wrappers' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
, - }), - ) - .add(extension) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createAppRootWrapperExtension({ - namespace: 'ns', - name: 'test', - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( -
- {children} -
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-root-wrapper', - namespace: 'ns', - name: 'test', - attachTo: { id: 'app/root', input: 'wrappers' }, - configSchema: schema, - disabled: true, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
Hello
, - }), - ) - .add(extension, { config: { name: 'Robin' } }) - .add( - createExtension({ - attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
}), - }), - ) - .render(); - - await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - await expect(screen.findByTestId('Robin-1')).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx deleted file mode 100644 index 4f70f29470..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx +++ /dev/null @@ -1,84 +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, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; -import { Expand } from '../types'; - -/** - * Creates an extension that renders a React wrapper at the app root, enclosing - * the app layout. This is useful for example for adding global React contexts - * and similar. - * - * @public - * @deprecated Use {@link AppRootWrapperBlueprint} instead. - */ -export function createAppRootWrapperExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-root-wrapper', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'wrappers' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: createAppRootWrapperExtension.componentDataRef, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** @public */ -export namespace createAppRootWrapperExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType> - >().with({ id: 'app.root.wrapper' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index a40ee3b069..f561a73dc6 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -15,74 +15,50 @@ */ import { lazy, ComponentType } from 'react'; -import { - AnyExtensionInputMap, - ResolvedExtensionInputs, - createExtension, - createExtensionDataRef, -} from '../wiring'; -import { Expand } from '../types'; -import { PortableSchema } from '../schema'; +import { createExtension, createExtensionDataRef } from '../wiring'; import { ComponentRef } from '../components'; /** @public */ -export function createComponentExtension< - TProps extends {}, - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { +export function createComponentExtension(options: { ref: ComponentRef; name?: string; disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; loader: | { - lazy: (values: { - config: TConfig; - inputs: Expand>; - }) => Promise>; + lazy: () => Promise>; } | { - sync: (values: { - config: TConfig; - inputs: Expand>; - }) => ComponentType; + sync: () => ComponentType; }; }) { return createExtension({ kind: 'component', - namespace: options.ref.id, - name: options.name, - attachTo: { id: 'app', input: 'components' }, - inputs: options.inputs, + name: options.name ?? options.ref.id, + attachTo: { id: 'api:app/components', input: 'components' }, disabled: options.disabled, - configSchema: options.configSchema, - output: { - component: createComponentExtension.componentDataRef, - }, - factory({ config, inputs }) { + output: [createComponentExtension.componentDataRef], + factory() { if ('sync' in options.loader) { - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, - impl: options.loader.sync({ config, inputs }) as ComponentType, - }, - }; + impl: options.loader.sync() as ComponentType, + }), + ]; } const lazyLoader = options.loader.lazy; const ExtensionComponent = lazy(() => - lazyLoader({ config, inputs }).then(Component => ({ + lazyLoader().then(Component => ({ default: Component, })), ) as unknown as ComponentType; - return { - component: { + return [ + createComponentExtension.componentDataRef({ ref: options.ref, impl: ExtensionComponent, - }, - }; + }), + ]; }, }); } diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx deleted file mode 100644 index d694440427..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ /dev/null @@ -1,67 +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 { IconComponent } from '@backstage/core-plugin-api'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createExtension, createExtensionDataRef } from '../wiring'; -import { RouteRef } from '../routing'; - -/** - * Helper for creating extensions for a nav item. - * - * @public - * @deprecated Use {@link NavItemBlueprint} instead. - */ -export function createNavItemExtension(options: { - namespace?: string; - name?: string; - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) { - const { routeRef, title, icon, namespace, name } = options; - return createExtension({ - namespace, - name, - kind: 'nav-item', - attachTo: { id: 'app/nav', input: 'items' }, - configSchema: createSchemaFromZod(z => - z.object({ - title: z.string().default(title), - }), - ), - output: { - navTarget: createNavItemExtension.targetDataRef, - }, - factory: ({ config }) => ({ - navTarget: { - title: config.title, - icon, - routeRef, - }, - }), - }); -} - -/** @public */ -export namespace createNavItemExtension { - // TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef - export const targetDataRef = createExtensionDataRef<{ - title: string; - icon: IconComponent; - routeRef: RouteRef; - }>().with({ id: 'core.nav-item.target' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx deleted file mode 100644 index 17187a339d..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx +++ /dev/null @@ -1,48 +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 from 'react'; -import { createNavLogoExtension } from './createNavLogoExtension'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); - -describe('createNavLogoExtension', () => { - it('creates the extension properly', () => { - expect( - createNavLogoExtension({ - name: 'test', - logoFull:
Logo Full
, - logoIcon:
Logo Icon
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'nav-logo', - name: 'test', - attachTo: { id: 'app/nav', input: 'logos' }, - disabled: false, - inputs: {}, - output: { - logos: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx deleted file mode 100644 index 02c32e7f34..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx +++ /dev/null @@ -1,57 +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 { createExtension, createExtensionDataRef } from '../wiring'; - -/** - * Helper for creating extensions for a nav logos. - * - * @public - * @deprecated Use {@link NavLogoBlueprint} instead. - */ -export function createNavLogoExtension(options: { - name?: string; - namespace?: string; - logoIcon: JSX.Element; - logoFull: JSX.Element; -}) { - const { logoIcon, logoFull } = options; - return createExtension({ - kind: 'nav-logo', - name: options?.name, - namespace: options?.namespace, - attachTo: { id: 'app/nav', input: 'logos' }, - output: { - logos: createNavLogoExtension.logoElementsDataRef, - }, - factory: () => { - return { - logos: { - logoIcon, - logoFull, - }, - }; - }, - }); -} - -/** @public */ -export namespace createNavLogoExtension { - export const logoElementsDataRef = createExtensionDataRef<{ - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }>().with({ id: 'core.nav-logo.logo-elements' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx deleted file mode 100644 index e5dd4027c0..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ /dev/null @@ -1,145 +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 from 'react'; -import { useAnalytics } from '@backstage/core-plugin-api'; -import { waitFor } from '@testing-library/react'; -import { PortableSchema } from '../schema'; -import { coreExtensionData, createExtensionInput } from '../wiring'; -import { createPageExtension } from './createPageExtension'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useAnalytics: jest.fn(), -})); - -describe('createPageExtension', () => { - it('creates the extension properly', () => { - const configSchema: PortableSchema<{ path: string }> = { - parse: jest.fn(), - schema: {} as any, - }; - - expect( - createPageExtension({ - name: 'test', - configSchema, - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - attachTo: { id: 'other', input: 'place' }, - disabled: true, - configSchema, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'other', input: 'place' }, - configSchema: expect.anything(), - override: expect.any(Function), - disabled: true, - inputs: { - first: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect( - createPageExtension({ - name: 'test', - defaultPath: '/here', - loader: async () =>
, - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - name: 'test', - kind: 'page', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: expect.anything(), - disabled: false, - inputs: {}, - output: { - element: expect.anything(), - path: expect.anything(), - routeRef: expect.anything(), - }, - factory: expect.any(Function), - toString: expect.any(Function), - override: expect.any(Function), - }); - }); - - it('capture page view event in analytics', async () => { - const captureEvent = jest.fn(); - - (useAnalytics as jest.Mock).mockReturnValue({ - captureEvent, - }); - - createExtensionTester( - createPageExtension({ - defaultPath: '/', - loader: async () =>
Component
, - }), - ).render(); - - await waitFor(() => - expect(captureEvent).toHaveBeenCalledWith( - '_ROUTABLE-EXTENSION-RENDERED', - '', - ), - ); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx deleted file mode 100644 index bef642e465..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ /dev/null @@ -1,98 +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 { ExtensionBoundary } from '../components'; -import { createSchemaFromZod, PortableSchema } from '../schema'; -import { - coreExtensionData, - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, -} from '../wiring'; -import { RouteRef } from '../routing'; -import { Expand } from '../types'; -import { ExtensionDefinition } from '../wiring/createExtension'; - -/** - * Helper for creating extensions for a routable React page component. - * - * @public - * @deprecated Use {@link PageBlueprint} instead. - */ -export function createPageExtension< - TConfig extends { path: string }, - TInputs extends AnyExtensionInputMap, ->( - options: ( - | { - defaultPath: string; - } - | { - configSchema: PortableSchema; - } - ) & { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; - }, -): ExtensionDefinition { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ path: z.string().default(options.defaultPath) }), - ) as PortableSchema); - - return createExtension({ - kind: 'page', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, - configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - routeRef: options.routeRef, - element: ( - - - - ), - }; - }, - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx deleted file mode 100644 index 2c16076fcf..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createSpecializedApp } from '@backstage/frontend-app-api'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { MockConfigApi } from '@backstage/test-utils'; -import { MemoryRouter } from 'react-router-dom'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData } from '../wiring/coreExtensionData'; -import { createExtension } from '../wiring/createExtension'; -import { createExtensionInput } from '../wiring/createExtensionInput'; -import { createExtensionOverrides } from '../wiring/createExtensionOverrides'; -import { createPageExtension } from './createPageExtension'; -import { createRouterExtension } from './createRouterExtension'; - -describe('createRouterExtension', () => { - it('works with simple options and no props', async () => { - const extension = createRouterExtension({ - namespace: 'test', - Component: ({ children }) => ( - -
{children}
-
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - attachTo: { id: 'app/root', input: 'router' }, - disabled: false, - inputs: {}, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
, - }), - ], - }), - ], - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router'), - ).resolves.toBeInTheDocument(); - }); - - it('works with complex options and props', async () => { - const schema = createSchemaFromZod(z => z.object({ name: z.string() })); - - const extension = createRouterExtension({ - namespace: 'test', - name: 'test', - configSchema: schema, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - Component: ({ inputs, config, children }) => ( - -
- {children} -
-
- ), - }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'app-router-component', - namespace: 'test', - name: 'test', - attachTo: { id: 'app/root', input: 'router' }, - configSchema: schema, - disabled: false, - inputs: { - children: createExtensionInput({ - element: coreExtensionData.reactElement, - }), - }, - output: { - component: expect.anything(), - }, - factory: expect.any(Function), - override: expect.any(Function), - toString: expect.any(Function), - }); - - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - extension, - createExtension({ - namespace: 'test', - attachTo: { - id: 'app-router-component:test/test', - input: 'children', - }, - output: { element: coreExtensionData.reactElement }, // doesn't matter - factory: () => ({ element:
}), - }), - createPageExtension({ - namespace: 'test', - defaultPath: '/', - loader: async () =>
, - }), - ], - }), - ], - config: new MockConfigApi({ - app: { - extensions: [ - { - 'app-router-component:test/test': { config: { name: 'Robin' } }, - }, - ], - }, - }), - }); - - render(app.createRoot()); - - await expect( - screen.findByTestId('test-contents'), - ).resolves.toBeInTheDocument(); - await expect( - screen.findByTestId('test-router-Robin-1'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx deleted file mode 100644 index 943711f5e0..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx +++ /dev/null @@ -1,84 +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, { ComponentType, PropsWithChildren } from 'react'; -import { PortableSchema } from '../schema/types'; -import { - AnyExtensionInputMap, - ExtensionDefinition, - ResolvedExtensionInputs, - createExtension, -} from '../wiring/createExtension'; -import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; -import { Expand } from '../types'; - -/** - * Creates an extension that replaces the router implementation at the app root. - * This is useful to be able to for example replace the BrowserRouter with a - * MemoryRouter in tests, or to add additional props to a BrowserRouter. - * - * @public - * @deprecated Use {@link RouterBlueprint} instead. - */ -export function createRouterExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - Component: ComponentType< - PropsWithChildren<{ - inputs: Expand>; - config: TConfig; - }> - >; -}): ExtensionDefinition { - return createExtension({ - kind: 'app-router-component', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'router' }, - configSchema: options.configSchema, - disabled: options.disabled, - inputs: options.inputs, - output: { - component: createRouterExtension.componentDataRef, - }, - factory({ inputs, config }) { - const Component = (props: PropsWithChildren<{}>) => { - return ( - - {props.children} - - ); - }; - return { - component: Component, - }; - }, - }); -} - -/** @public */ -export namespace createRouterExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType> - >().with({ id: 'app.router.wrapper' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx deleted file mode 100644 index d8835ae788..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx +++ /dev/null @@ -1,47 +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 from 'react'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { screen } from '@testing-library/react'; -import { createSignInPageExtension } from './createSignInPageExtension'; -import { coreExtensionData, createExtension } from '../wiring'; - -describe('createSignInPageExtension', () => { - it('renders a sign-in page', async () => { - const SignInPage = createSignInPageExtension({ - name: 'test', - loader: async () => () =>
, - }); - - createExtensionTester( - createExtension({ - name: 'dummy', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element:
}), - }), - ) - .add(SignInPage) - .render(); - - await expect( - screen.findByTestId('sign-in-page'), - ).resolves.toBeInTheDocument(); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx deleted file mode 100644 index 3f41202f11..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx +++ /dev/null @@ -1,84 +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, { ComponentType, lazy } from 'react'; -import { ExtensionBoundary } from '../components'; -import { PortableSchema } from '../schema'; -import { - createExtension, - ResolvedExtensionInputs, - AnyExtensionInputMap, - createExtensionDataRef, - ExtensionDefinition, -} from '../wiring'; -import { Expand } from '../types'; -import { SignInPageProps } from '@backstage/core-plugin-api'; - -/** - * - * @public - * @deprecated Use {@link SignInPageBlueprint} instead. - */ -export function createSignInPageExtension< - TConfig extends {}, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - configSchema?: PortableSchema; - disabled?: boolean; - inputs?: TInputs; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise>; -}): ExtensionDefinition { - return createExtension({ - kind: 'sign-in-page', - namespace: options?.namespace, - name: options?.name, - attachTo: options.attachTo ?? { id: 'app/root', input: 'signInPage' }, - configSchema: options.configSchema, - inputs: options.inputs, - disabled: options.disabled, - output: { - component: createSignInPageExtension.componentDataRef, - }, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config, inputs }) - .then(component => ({ default: component })), - ); - - return { - component: props => ( - - - - ), - }; - }, - }); -} - -/** @public */ -export namespace createSignInPageExtension { - export const componentDataRef = createExtensionDataRef< - ComponentType - >().with({ id: 'core.sign-in-page.component' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts deleted file mode 100644 index e523fd9c05..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ /dev/null @@ -1,45 +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 { createExtension, createExtensionDataRef } from '../wiring'; -import { AppTheme } from '@backstage/core-plugin-api'; - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -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 }), - }); -} - -/** - * @public - * @deprecated Use {@link ThemeBlueprint} instead. - */ -export namespace createThemeExtension { - export const themeDataRef = createExtensionDataRef().with({ - id: 'core.theme.theme', - }); -} diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts deleted file mode 100644 index 019c565cbd..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts +++ /dev/null @@ -1,137 +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 { - createTranslationRef, - createTranslationMessages, - createTranslationResource, -} from '@backstage/core-plugin-api/alpha'; -import { createTranslationExtension } from './createTranslationExtension'; - -const translationRef = createTranslationRef({ - id: 'test', - messages: { - a: 'a', - b: 'b', - }, -}); - -describe('createTranslationExtension', () => { - it('creates a translation message extension', () => { - const messages = createTranslationMessages({ - ref: translationRef, - messages: { - a: 'A', - }, - }); - const extension = createTranslationExtension({ resource: messages }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ - resource: messages, - }); - }); - - it('creates a translation resource extension', () => { - const resource = createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }); - const extension = createTranslationExtension({ resource }); - - expect(extension).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - override: expect.any(Function), - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - - expect((extension as any).factory({} as any)).toEqual({ resource }); - }); - - it('creates a translation resource extension with a name', () => { - expect( - createTranslationExtension({ - name: 'sv', - resource: createTranslationResource({ - ref: translationRef, - translations: { - sv: () => - Promise.resolve({ - default: createTranslationMessages({ - ref: translationRef, - messages: { - a: 'Ä', - b: 'Ö', - }, - }), - }), - }, - }), - }), - ).toEqual({ - $$type: '@backstage/ExtensionDefinition', - version: 'v1', - kind: 'translation', - namespace: 'test', - override: expect.any(Function), - name: 'sv', - attachTo: { id: 'app', input: 'translations' }, - disabled: false, - inputs: {}, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: expect.any(Function), - toString: expect.any(Function), - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts deleted file mode 100644 index c4d2f6037d..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts +++ /dev/null @@ -1,48 +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 { TranslationMessages, TranslationResource } from '../translation'; -import { createExtension, createExtensionDataRef } from '../wiring'; - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export function createTranslationExtension(options: { - name?: string; - resource: TranslationResource | TranslationMessages; -}) { - return createExtension({ - kind: 'translation', - namespace: options.resource.id, - name: options.name, - attachTo: { id: 'app', input: 'translations' }, - output: { - resource: createTranslationExtension.translationDataRef, - }, - factory: () => ({ resource: options.resource }), - }); -} - -/** - * @public - * @deprecated Use {@link TranslationBlueprint} instead. - */ -export namespace createTranslationExtension { - export const translationDataRef = createExtensionDataRef< - TranslationResource | TranslationMessages - >().with({ id: 'core.translation.translation' }); -} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 562cb728cb..f75c7474ac 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -14,14 +14,4 @@ * limitations under the License. */ -export { createApiExtension } from './createApiExtension'; -export { createAppRootElementExtension } from './createAppRootElementExtension'; -export { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; -export { createRouterExtension } from './createRouterExtension'; -export { createPageExtension } from './createPageExtension'; -export { createNavItemExtension } from './createNavItemExtension'; -export { createNavLogoExtension } from './createNavLogoExtension'; -export { createSignInPageExtension } from './createSignInPageExtension'; -export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; -export { createTranslationExtension } from './createTranslationExtension'; diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index 9e63515ae4..85f9d5c44a 100644 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -20,8 +20,7 @@ import zodToJsonSchema from 'zod-to-json-schema'; import { PortableSchema } from './types'; /** - * @public - * @deprecated Use the `config.schema` option of `createExtension` instead, or use `createExtensionBlueprint`. + * @internal */ export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, diff --git a/packages/frontend-plugin-api/src/schema/index.ts b/packages/frontend-plugin-api/src/schema/index.ts index 7f21c4e07d..a8f92a37f4 100644 --- a/packages/frontend-plugin-api/src/schema/index.ts +++ b/packages/frontend-plugin-api/src/schema/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export { createSchemaFromZod } from './createSchemaFromZod'; export { type PortableSchema } from './types'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index f9e39e98de..5e8aa1c57a 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -18,53 +18,49 @@ import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; +import { PortableSchema } from '../schema'; const stringDataRef = createExtensionDataRef().with({ id: 'string' }); const numberDataRef = createExtensionDataRef().with({ id: 'number' }); +const booleanDataRef = createExtensionDataRef().with({ + id: 'boolean', +}); function unused(..._any: any[]) {} describe('createExtension', () => { it('should create an extension with a simple output', () => { const baseConfig = { - namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - }, + output: [stringDataRef], }; const extension = createExtension({ ...baseConfig, factory() { - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); - // When declared as an error function without a block the TypeScript errors - // are a more specific and will often point at the property that is problematic. + // Member arrow function declaration + createExtension({ + ...baseConfig, + factory: () => [ + stringDataRef( + // @ts-expect-error + 3, + ), + ], + }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); + // @ts-expect-error createExtension({ ...baseConfig, - factory: () => - // @ts-expect-error - ({ - bar: 'bar', - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -79,39 +75,37 @@ describe('createExtension', () => { 'bar', }); - // When declared as a function with a block the TypeScript error will instead - // be tied to the factory function declaration itself, but the error messages - // is still helpful and points to part of the return type that is problematic. + // Method declaration createExtension({ ...baseConfig, - // @ts-expect-error factory() { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory() { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory() { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory() { - return {}; + return; }, }); createExtension({ @@ -122,36 +116,37 @@ describe('createExtension', () => { }, }); + // Member function declaration createExtension({ ...baseConfig, - // @ts-expect-error factory: () => { - return { - foo: 3, - }; + return [ + stringDataRef( + // @ts-expect-error + 3, + ), + ]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return [numberDataRef(3)]; + }, + }); + // @ts-expect-error + createExtension({ + ...baseConfig, + factory: () => { + return []; }, }); createExtension({ ...baseConfig, // @ts-expect-error factory: () => { - return { - bar: 'bar', - }; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; - }, - }); - createExtension({ - ...baseConfig, - // @ts-expect-error - factory: () => { - return {}; + return; }, }); createExtension({ @@ -165,54 +160,28 @@ describe('createExtension', () => { it('should create an extension with a some optional output', () => { const baseConfig = { - namespace: 'test', attachTo: { id: 'root', input: 'default' }, - output: { - foo: stringDataRef, - bar: stringDataRef.optional(), - }, + output: [stringDataRef, numberDataRef.optional()], }; const extension = createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - }), + factory: () => [stringDataRef('bar')], }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 'baz', - }), + factory: () => [stringDataRef('bar'), numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 3, - }), + factory: () => [numberDataRef(3)], }); // @ts-expect-error createExtension({ ...baseConfig, - factory: () => ({ - foo: 'bar', - bar: 3, - }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({ bar: 'bar' }), - }); - createExtension({ - ...baseConfig, - factory: () => - // @ts-expect-error - ({}), + factory: () => [], }); createExtension({ ...baseConfig, @@ -235,68 +204,57 @@ describe('createExtension', () => { it('should create an extension with input', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { - mixed: createExtensionInput({ - required: stringDataRef, - optional: stringDataRef.optional(), - }), - onlyRequired: createExtensionInput({ - required: stringDataRef, - }), - onlyOptional: createExtensionInput({ - optional: stringDataRef.optional(), - }), - }, - output: { - foo: stringDataRef, + mixed: createExtensionInput([stringDataRef, numberDataRef.optional()]), + onlyRequired: createExtensionInput([stringDataRef]), + onlyOptional: createExtensionInput([stringDataRef.optional()]), }, + output: [stringDataRef], factory({ inputs }) { - const a1: string = inputs.mixed?.[0].output.required; + const a1: string = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a2: number = inputs.mixed?.[0].output.required; + const a2: number = inputs.mixed?.[0].get(stringDataRef); // @ts-expect-error - const a3: any = inputs.mixed?.[0].output.nonExistent; + const a3: any = inputs.mixed?.[0].get(booleanDataRef); unused(a1, a2, a3); - const b1: string | undefined = inputs.mixed?.[0].output.optional; + const b1: number | undefined = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b2: string = inputs.mixed?.[0].output.optional; + const b2: string = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b3: number = inputs.mixed?.[0].output.optional; + const b3: number = inputs.mixed?.[0].get(numberDataRef); // @ts-expect-error - const b4: number | undefined = inputs.mixed?.[0].output.optional; + const b4: string | undefined = inputs.mixed?.[0].get(numberDataRef); unused(b1, b2, b3, b4); - const c1: string = inputs.onlyRequired?.[0].output.required; + const c1: string = inputs.onlyRequired?.[0].get(stringDataRef); // @ts-expect-error - const c2: number = inputs.onlyRequired?.[0].output.required; + const c2: number = inputs.onlyRequired?.[0].get(stringDataRef); unused(c1, c2); - const d1: string | undefined = inputs.onlyOptional?.[0].output.optional; + const d1: string | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d2: string = inputs.onlyOptional?.[0].output.optional; + const d2: string = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d3: number = inputs.onlyOptional?.[0].output.optional; + const d3: number = inputs.onlyOptional?.[0].get(stringDataRef); // @ts-expect-error - const d4: number | undefined = inputs.onlyOptional?.[0].output.optional; + const d4: number | undefined = + inputs.onlyOptional?.[0].get(stringDataRef); unused(d1, d2, d3, d4); - return { - foo: 'bar', - }; + return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v1', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); expect(String(extension)).toBe( - 'ExtensionDefinition{namespace=test,attachTo=root@default}', + 'ExtensionDefinition{attachTo=root@default}', ); }); it('should create an extension with config', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, config: { schema: { @@ -322,13 +280,18 @@ describe('createExtension', () => { return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); expect(String(extension)).toBe( - 'ExtensionDefinition{namespace=test,attachTo=root@default}', + 'ExtensionDefinition{attachTo=root@default}', ); expect( - extension.configSchema?.parse({ + ( + (extension as any).configSchema as PortableSchema< + (typeof extension.T)['config'], + (typeof extension.T)['configInput'] + > + )?.parse({ foo: 'x', bar: 'y', baz: 'z', @@ -341,7 +304,12 @@ describe('createExtension', () => { baz: 'z', }); expect( - extension.configSchema?.parse({ + ( + (extension as any).configSchema as PortableSchema< + (typeof extension.T)['config'], + (typeof extension.T)['configInput'] + > + )?.parse({ foo: 'x', }), ).toEqual({ @@ -358,7 +326,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -370,7 +337,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -382,7 +348,6 @@ describe('createExtension', () => { // Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], factory() { @@ -394,7 +359,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], factory() { @@ -405,7 +369,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -416,7 +379,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], factory() { @@ -427,7 +389,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], factory() { @@ -441,7 +402,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -453,7 +413,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -465,7 +424,6 @@ describe('createExtension', () => { // Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], *factory() { @@ -478,7 +436,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], *factory() { @@ -490,7 +447,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -502,7 +458,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], *factory() { @@ -514,7 +469,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], *factory() { @@ -527,7 +481,6 @@ describe('createExtension', () => { it('should support new form of inputs', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { header: createExtensionInput([stringDataRef.optional()], { @@ -562,7 +515,6 @@ describe('createExtension', () => { describe('overrides', () => { it('should allow overriding of config and merging', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], config: { @@ -591,7 +543,6 @@ describe('createExtension', () => { it('should allow overriding of outputs', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], inputs: { @@ -631,7 +582,6 @@ describe('createExtension', () => { it('should allow overriding the factory function and calling the original factory', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], config: { @@ -663,7 +613,6 @@ describe('createExtension', () => { it('should allow overriding the returned values from the parent factory', () => { const testExtension = createExtension({ kind: 'thing', - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], config: { @@ -688,13 +637,12 @@ describe('createExtension', () => { const tester = createExtensionTester(overridden); - expect(tester.data(numberDataRef)).toBe(43); + expect(tester.get(numberDataRef)).toBe(43); }); it('should work functionally with overrides', () => { const testExtension = createExtension({ kind: 'thing', - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], config: { @@ -722,15 +670,386 @@ describe('createExtension', () => { }, }); - expect(createExtensionTester(overriden).data(stringDataRef)).toBe( + expect(createExtensionTester(overriden).get(stringDataRef)).toBe( 'foo-boom-override-hello', ); expect( createExtensionTester(overriden, { config: { foo: 'hello', bar: 'world' }, - }).data(stringDataRef), + }).get(stringDataRef), ).toBe('foo-hello-override-world'); }); + + it('should be able to override input values', () => { + const outputRef = createExtensionDataRef().with({ + id: 'output', + }); + const testDataRef1 = createExtensionDataRef().with({ + id: 'test1', + }); + const testDataRef2 = createExtensionDataRef().with({ + id: 'test2', + }); + + const subject = createExtension({ + name: 'subject', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + opt: createExtensionInput([testDataRef1.optional()], { + singleton: true, + optional: true, + }), + single: createExtensionInput( + [testDataRef1, testDataRef2.optional()], + { + singleton: true, + }, + ), + multi: createExtensionInput([testDataRef1]), + }, + output: [outputRef], + factory({ inputs }) { + return [ + outputRef({ + opt: inputs.opt?.get(testDataRef1) ?? 'none', + single: inputs.single.get(testDataRef1), + singleOpt: inputs.single.get(testDataRef2) ?? 'none', + multi: inputs.multi + .map(i => `${i.node.spec.id}=${i.get(testDataRef1)}`) + .join(','), + }), + ]; + }, + }); + + const optExt = createExtension({ + name: 'o', + attachTo: { id: 'subject', input: 'opt' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-opt')], + }); + + const singleExt = createExtension({ + name: 's', + attachTo: { id: 'subject', input: 'single' }, + output: [testDataRef1, testDataRef2.optional()], + factory: () => [testDataRef1('orig-single')], + }); + + const multi1Ext = createExtension({ + name: 'm1', + attachTo: { id: 'subject', input: 'multi' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-multi1')], + }); + + const multi2Ext = createExtension({ + name: 'm2', + attachTo: { id: 'subject', input: 'multi' }, + output: [testDataRef1], + factory: () => [testDataRef1('orig-multi2')], + }); + + expect( + createExtensionTester(subject) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm1=orig-multi1,m2=orig-multi2', + }); + + // All values provided + expect( + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + opt: [testDataRef1('opt')], + single: [testDataRef1('single'), testDataRef2('singleOpt')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'opt', + single: 'single', + singleOpt: 'singleOpt', + multi: 'm1=multi1,m2=multi2', + }); + + // Minimal values provided + expect( + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + single: [testDataRef1('single')], + multi: [], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'none', + single: 'single', + singleOpt: 'none', + multi: '', + }); + + // Forward inputs directly + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm1=orig-multi1,m2=orig-multi2', + }); + + // Forward inputs separately + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi, + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm1=orig-multi1,m2=orig-multi2', + }); + + // Reordering inputs + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: [inputs.multi[1], inputs.multi[0]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm2=orig-multi2,m1=orig-multi1', + }); + + // Filter out inputs + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi.filter(i => i.node.spec.id.endsWith('2')), + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm2=orig-multi2', + }); + + // Overriding based on original input + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + single: [ + testDataRef1(`override-${inputs.single.get(testDataRef1)}`), + testDataRef2('new-singleOpt'), + ], + multi: inputs.multi.map(i => [ + testDataRef1(`override-${i.get(testDataRef1)}`), + ]), + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toEqual({ + opt: 'none', + single: 'override-orig-single', + singleOpt: 'new-singleOpt', + multi: 'm1=override-orig-multi1,m2=override-orig-multi2', + }); + + // Mismatched input override length + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + multi: [[testDataRef1('multi1')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', override data provided for input 'multi' must match the length of the original inputs"`, + ); + + // Mix forward and data override + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + multi: [inputs.multi[0], [testDataRef1('multi2')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', override data for input 'multi' may not mix forwarded inputs with data overrides"`, + ); + + // Required input not provided + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + single: [testDataRef2('singleOpt')], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', missing required extension data value(s) 'test1'"`, + ); + + // Wrong value provided + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory) { + return originalFactory({ + inputs: { + // @ts-expect-error + opt: [testDataRef2('opt')], + // @ts-expect-error + single: [testDataRef1('single'), outputRef({})], + multi: [ + // @ts-expect-error + [testDataRef2('multi1')], + ], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .get(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', extension data 'test2' was provided but not declared"`, + ); + }); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index f2d3d906ab..8c79758395 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,77 +14,24 @@ * limitations under the License. */ -import { AppNode } from '../apis'; -import { PortableSchema, createSchemaFromZod } from '../schema'; +import { ApiHolder, AppNode } from '../apis'; +import { PortableSchema } from '../schema'; import { Expand } from '../types'; -import { createDataContainer } from './createExtensionBlueprint'; +import { + ResolveInputValueOverrides, + resolveInputOverrides, +} from './resolveInputOverrides'; +import { + ExtensionDataContainer, + createExtensionDataContainer, +} from './createExtensionDataContainer'; import { AnyExtensionDataRef, - ExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; -import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput'; +import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; - -/** - * @public - * @deprecated Extension data maps will be removed. - */ -export type AnyExtensionDataMap = { - [name in string]: AnyExtensionDataRef; -}; - -/** - * @public - * @deprecated This type will be removed. - */ -export type AnyExtensionInputMap = { - [inputName in string]: LegacyExtensionInput< - AnyExtensionDataMap, - { optional: boolean; singleton: boolean } - >; -}; - -/** - * Converts an extension data map into the matching concrete data values type. - * @public - * @deprecated Extension data maps will be removed. - */ -export type ExtensionDataValues = { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? never - : DataName]: TExtensionData[DataName]['T']; -} & { - [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends { - optional: true; - } - ? DataName - : never]?: TExtensionData[DataName]['T']; -}; - -/** @public */ -export type ExtensionDataContainer = - Iterable< - UExtensionData extends ExtensionDataRef< - infer IData, - infer IId, - infer IConfig - > - ? IConfig['optional'] extends true - ? never - : ExtensionDataValue - : never - > & { - get( - ref: ExtensionDataRef, - ): UExtensionData extends ExtensionDataRef - ? IConfig['optional'] extends true - ? IData | undefined - : IData - : never; - }; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; /** * Convert a single extension input into a matching resolved input. @@ -96,11 +43,6 @@ export type ResolvedExtensionInput< ? { node: AppNode; } & ExtensionDataContainer - : TExtensionInput['extensionData'] extends AnyExtensionDataMap - ? { - node: AppNode; - output: ExtensionDataValues; - } : never; /** @@ -109,7 +51,7 @@ export type ResolvedExtensionInput< */ export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput | LegacyExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -119,30 +61,28 @@ export type ResolvedExtensionInputs< : Expand | undefined>; }; -/** - * @public - * @deprecated This way of structuring the options is deprecated, this type will be removed in the future - */ -export interface LegacyCreateExtensionOptions< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, -> { - kind?: string; - namespace?: string; - name?: string; - attachTo: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - output: TOutput; - configSchema?: PortableSchema; - factory(context: { - node: AppNode; - config: TConfig; - inputs: Expand>; - }): Expand>; -} +type ToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void + ? I + : never; + +type PopUnion = ToIntersection< + U extends any ? () => U : never +> extends () => infer R + ? [rest: Exclude, next: R] + : undefined; + +/** @ignore */ +type JoinStringUnion< + U, + TDiv extends string = ', ', + TResult extends string = '', +> = PopUnion extends [infer IRest extends string, infer INext extends string] + ? TResult extends '' + ? JoinStringUnion + : JoinStringUnion + : TResult; /** @ignore */ export type VerifyExtensionFactoryOutput< @@ -158,18 +98,12 @@ export type VerifyExtensionFactoryOutput< ? [IRequiredOutputIds] extends [UFactoryOutput['id']] ? [UFactoryOutput['id']] extends [UDeclaredOutput['id']] ? {} - : { - 'Error: The extension factory has undeclared output(s)': Exclude< - UFactoryOutput['id'], - UDeclaredOutput['id'] - >; - } - : { - 'Error: The extension factory is missing the following output(s)': Exclude< - IRequiredOutputIds, - UFactoryOutput['id'] - >; - } + : `Error: The extension factory has undeclared output(s): ${JoinStringUnion< + Exclude + >}` + : `Error: The extension factory is missing the following output(s): ${JoinStringUnion< + Exclude + >}` : never; /** @public */ @@ -199,6 +133,7 @@ export type CreateExtensionOptions< }; factory(context: { node: AppNode; + apis: ApiHolder; config: { [key in keyof TConfigSchema]: z.infer>; }; @@ -207,27 +142,27 @@ export type CreateExtensionOptions< } & VerifyExtensionFactoryOutput; /** @public */ -export interface ExtensionDefinition< - TConfig, - TConfigInput = TConfig, - UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< +export type ExtensionDefinitionParameters = { + kind?: string; + namespace?: string; + name?: string; + configInput?: { [K in string]: any }; + config?: { [K in string]: any }; + output?: AnyExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput< AnyExtensionDataRef, { optional: boolean; singleton: boolean } >; - } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, -> { + }; +}; + +/** @public */ +export type ExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> = { $$type: '@backstage/ExtensionDefinition'; - readonly kind?: TKind; - readonly namespace?: TNamespace; - readonly name?: TName; - readonly attachTo: { id: string; input: string }; - readonly disabled: boolean; - readonly configSchema?: PortableSchema; + readonly T: T; override< TExtensionConfigSchema extends { @@ -246,90 +181,95 @@ export interface ExtensionDefinition< attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & + [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; config?: { schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & + [KName in keyof T['config']]?: `Error: Config key '${KName & string}' is already defined in parent schema`; }; }; factory( originalFactory: (context?: { - config?: TConfig; - inputs?: Expand>; - }) => ExtensionDataContainer, + config?: T['config']; + inputs?: ResolveInputValueOverrides>; + }) => ExtensionDataContainer>, context: { node: AppNode; - config: TConfig & { + apis: ApiHolder; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable; } & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, UFactoryOutput >, - ): ExtensionDefinition< - { + ): ExtensionDefinition<{ + kind: T['kind']; + namespace: T['namespace']; + name: T['name']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; - } & TConfig, - z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - }> - > & - TConfigInput, - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs, - TKind, - TNamespace, - TName - >; -} + }; + configInput: T['configInput'] & + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >; + }>; +}; /** @internal */ export type InternalExtensionDefinition< - TConfig, - TConfigInput = TConfig, - UOutput extends AnyExtensionDataRef = AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - } = {}, - TKind extends string | undefined = string | undefined, - TNamespace extends string | undefined = string | undefined, - TName extends string | undefined = string | undefined, -> = ExtensionDefinition< - TConfig, - TConfigInput, - UOutput, - TInputs, - TKind, - TNamespace, - TName -> & - ( + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> = ExtensionDefinition & { + readonly kind?: string; + readonly namespace?: string; + readonly name?: string; + readonly attachTo: { id: string; input: string }; + readonly disabled: boolean; + readonly configSchema?: PortableSchema; +} & ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; factory(context: { node: AppNode; - config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + apis: ApiHolder; + config: object; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; @@ -342,7 +282,8 @@ export type InternalExtensionDefinition< readonly output: Array; factory(context: { node: AppNode; - config: TConfig; + apis: ApiHolder; + config: object; inputs: ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput< AnyExtensionDataRef, @@ -354,13 +295,10 @@ export type InternalExtensionDefinition< ); /** @internal */ -export function toInternalExtensionDefinition( - overrides: ExtensionDefinition, -): InternalExtensionDefinition { - const internal = overrides as InternalExtensionDefinition< - TConfig, - TConfigInput - >; +export function toInternalExtensionDefinition< + T extends ExtensionDefinitionParameters, +>(overrides: ExtensionDefinition): InternalExtensionDefinition { + const internal = overrides as InternalExtensionDefinition; if (internal.$$type !== '@backstage/ExtensionDefinition') { throw new Error( `Invalid extension definition instance, bad type '${internal.$$type}'`, @@ -392,49 +330,37 @@ export function createExtension< >( options: CreateExtensionOptions< TKind, - TNamespace, + undefined, TName, UOutput, TInputs, TConfigSchema, UFactoryOutput >, -): ExtensionDefinition< - { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >, - UOutput, - TInputs, - string | undefined extends TKind ? undefined : TKind, - string | undefined extends TNamespace ? undefined : TNamespace, - string | undefined extends TName ? undefined : TName ->; +): ExtensionDefinition<{ + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; /** * @public - * @deprecated - use the array format of `output` instead, see TODO-doc-link + * @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */ export function createExtension< - TOutput extends AnyExtensionDataMap, - TInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, ->( - options: LegacyCreateExtensionOptions< - TOutput, - TInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition; -export function createExtension< - const TKind extends string | undefined, - const TNamespace extends string | undefined, - const TName extends string | undefined, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -442,72 +368,117 @@ export function createExtension< { optional: boolean; singleton: boolean } >; }, - TLegacyInputs extends AnyExtensionInputMap, - TConfig, - TConfigInput, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, >( - options: - | CreateExtensionOptions< - TKind, - TNamespace, - TName, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput - > - | LegacyCreateExtensionOptions< - AnyExtensionDataMap, - TLegacyInputs, - TConfig, - TConfigInput - >, -): ExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema + options: CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; +export function createExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, +>( + options: CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + config: string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}> { + type T = { + config: string extends keyof TConfigSchema ? {} : { [key in keyof TConfigSchema]: z.infer>; - }), - TConfigInput & - (string extends keyof TConfigSchema + }; + configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >), - UOutput, - TInputs, - TKind, - TNamespace, - TName -> { - if ('configSchema' in options && 'config' in options) { - throw new Error(`Cannot provide both configSchema and config.schema`); - } - let configSchema: PortableSchema | undefined; - if ('configSchema' in options) { - configSchema = options.configSchema; - } - if ('config' in options) { - const newConfigSchema = options.config?.schema; - configSchema = - newConfigSchema && - createSchemaFromZod(innerZ => - innerZ.object( - Object.fromEntries( - Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]), - ), + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; + }; + + const schemaDeclaration = options.config?.schema; + const configSchema = + schemaDeclaration && + createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(schemaDeclaration).map(([k, v]) => [k, v(innerZ)]), ), - ); - } + ), + ); return { $$type: '@backstage/ExtensionDefinition', - version: Symbol.iterator in options.output ? 'v2' : 'v1', + version: 'v2', + T: undefined as unknown as T, kind: options.kind, namespace: options.namespace, name: options.name, @@ -531,74 +502,7 @@ export function createExtension< parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override: < - TExtensionConfigSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, - UOverrideFactoryOutput extends ExtensionDataValue, - UNewOutput extends AnyExtensionDataRef, - TExtraInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, - >(overrideOptions: { - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TExtraInputs; - output?: Array; - config?: { - schema: TExtensionConfigSchema; - }; - factory( - originalFactory: (context?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs?: Expand>; - }) => ExtensionDataContainer, - context: { - node: AppNode; - config: { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - } & { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs: Expand>; - }, - ): Iterable; - }): ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - } & { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject< - { - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - } & { - [key in keyof TConfigSchema]: ReturnType; - } - > - >, - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs, - TKind, - TNamespace, - TName - > => { + override(overrideOptions) { if (!Array.isArray(options.output)) { throw new Error( 'Cannot override an extension that is not declared using the new format with outputs as an array', @@ -613,12 +517,6 @@ export function createExtension< TConfigSchema, UFactoryOutput >; - const overrideNewConfigSchema = overrideOptions.config?.schema; - - const schema = { - ...newOptions.config?.schema, - ...overrideNewConfigSchema, - } as TConfigSchema & TExtensionConfigSchema; return createExtension({ kind: newOptions.kind, @@ -627,72 +525,61 @@ export function createExtension< attachTo: overrideOptions.attachTo ?? newOptions.attachTo, disabled: overrideOptions.disabled ?? newOptions.disabled, inputs: { ...overrideOptions.inputs, ...newOptions.inputs }, - output: overrideOptions.output ?? newOptions.output, - config: Object.keys(schema).length === 0 ? undefined : { schema }, - factory: ({ node, config, inputs }) => { + output: (overrideOptions.output ?? + newOptions.output) as AnyExtensionDataRef[], + config: + newOptions.config || overrideOptions.config + ? { + schema: { + ...newOptions.config?.schema, + ...overrideOptions.config?.schema, + }, + } + : undefined, + factory: ({ node, apis, config, inputs }) => { if (!overrideOptions.factory) { return newOptions.factory({ node, - config, - inputs: inputs as unknown as Expand< - ResolvedExtensionInputs - >, + apis, + config: config as any, + inputs: inputs as any, }); } const parentResult = overrideOptions.factory( - (innerContext?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs?: Expand>; - }): ExtensionDataContainer => { - return createDataContainer( + (innerContext): ExtensionDataContainer => { + return createExtensionDataContainer( newOptions.factory({ node, - config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden + apis, + config: (innerContext?.config ?? config) as any, + inputs: resolveInputOverrides( + newOptions.inputs, + inputs, + innerContext?.inputs, + ) as any, }) as Iterable, + newOptions.output, ); }, { node, - config, - inputs, + apis, + config: config as any, + inputs: inputs as any, }, ); - const deduplicatedResult = new Map(); + const deduplicatedResult = new Map< + string, + ExtensionDataValue + >(); for (const item of parentResult) { deduplicatedResult.set(item.id, item); } - return deduplicatedResult.values() as Iterable; + return deduplicatedResult.values(); }, - } as CreateExtensionOptions); + }) as ExtensionDefinition; }, - } as InternalExtensionDefinition< - TConfig & - (string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }), - TConfigInput & - (string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - >), - UOutput, - TInputs, - TKind, - TNamespace, - TName - >; + } as InternalExtensionDefinition; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index b9d1475074..fc77c63e97 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -17,23 +17,30 @@ import React from 'react'; import { coreExtensionData } from './coreExtensionData'; import { createExtensionBlueprint } from './createExtensionBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; -import { createExtensionDataRef } from './createExtensionDataRef'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { + ExtensionDataValue, + createExtensionDataRef, +} from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; import { ExtensionDefinition, toInternalExtensionDefinition, } from './createExtension'; +import { createExtensionDataContainer } from './createExtensionDataContainer'; function unused(..._any: any[]) {} -function factoryOutput(ext: ExtensionDefinition) { +function factoryOutput(ext: ExtensionDefinition, inputs: unknown = undefined) { const int = toInternalExtensionDefinition(ext); if (int.version !== 'v2') { throw new Error('Expected v2 extension'); } - return Array.from(int.factory({} as any)); + return Array.from(int.factory({ inputs } as any)); } describe('createExtensionBlueprint', () => { @@ -73,7 +80,9 @@ describe('createExtensionBlueprint', () => { version: 'v2', }); - const { container } = createExtensionTester(extension).render(); + const { container } = renderInTestApp( + createExtensionTester(extension).reactElement(), + ); expect(container.querySelector('h1')).toHaveTextContent('Hello, world!'); }); @@ -113,7 +122,9 @@ describe('createExtensionBlueprint', () => { version: 'v2', }); - const { container } = createExtensionTester(extension).render(); + const { container } = renderInTestApp( + createExtensionTester(extension).reactElement(), + ); expect(container.querySelector('h1')).toHaveTextContent('Hello, world!'); }); @@ -138,7 +149,9 @@ describe('createExtensionBlueprint', () => { expect(extension).toBeDefined(); - const { container } = createExtensionTester(extension).render(); + const { container } = renderInTestApp( + createExtensionTester(extension).reactElement(), + ); expect(container.querySelector('h1')).toHaveTextContent('Hello, world!'); }); @@ -209,13 +222,15 @@ describe('createExtensionBlueprint', () => { expect.assertions(4); - createExtensionTester(extension, { - config: { - something: 'something new!', - text: 'Hello, world!', - defaulted: 'lolz', - }, - }).render(); + renderInTestApp( + createExtensionTester(extension, { + config: { + something: 'something new!', + text: 'Hello, world!', + defaulted: 'lolz', + }, + }).reactElement(), + ); }); it('should not allow overlapping config keys', () => { @@ -284,12 +299,14 @@ describe('createExtensionBlueprint', () => { expect.assertions(2); - createExtensionTester(extension, { - config: { - something: 'something new!', - defaulted: 'lolz', - }, - }).render(); + renderInTestApp( + createExtensionTester(extension, { + config: { + something: 'something new!', + defaulted: 'lolz', + }, + }).reactElement(), + ); }); it('should allow getting inputs properly', () => { @@ -335,6 +352,257 @@ describe('createExtensionBlueprint', () => { expect(true).toBe(true); }); + it('should be able to override inputs when calling original factory', () => { + const outputRef = createExtensionDataRef().with({ id: 'output' }); + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + const Blueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + inputs: { + opt: createExtensionInput([testDataRef1.optional()], { + singleton: true, + optional: true, + }), + single: createExtensionInput([testDataRef1, testDataRef2.optional()], { + singleton: true, + }), + multi: createExtensionInput([testDataRef1]), + }, + output: [outputRef], + factory(_, { inputs }) { + return [ + outputRef({ + opt: inputs.opt?.get(testDataRef1) ?? 'none', + single: inputs.single.get(testDataRef1), + singleOpt: inputs.single.get(testDataRef2) ?? 'none', + multi: inputs.multi.map(i => i.get(testDataRef1)).join(','), + }), + ]; + }, + }); + + const mockInput = (node: string, ...data: ExtensionDataValue[]) => + Object.assign(createExtensionDataContainer(data), { + node, + }); + const mockParentInputs = { + opt: mockInput('node-opt', testDataRef1('orig-opt')), + single: mockInput('node-single', testDataRef1('orig-single')), + multi: [ + mockInput('node-multi1', testDataRef1('orig-multi1')), + mockInput('node-multi2', testDataRef1('orig-multi2')), + ], + }; + + // All values provided + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + opt: [testDataRef1('opt')], + single: [testDataRef1('single'), testDataRef2('singleOpt')], + multi: [[testDataRef1('multi1')], [testDataRef1('multi2')]], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'opt', + single: 'single', + singleOpt: 'singleOpt', + multi: 'multi1,multi2', + }), + ]); + + // Minimal values provided + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + single: [testDataRef1('single')], + multi: [], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'none', + single: 'single', + singleOpt: 'none', + multi: '', + }), + ]); + + // Mismatched input override length + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + ...inputs, + multi: [[testDataRef1('multi1')]], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"override data provided for input 'multi' must match the length of the original inputs"`, + ); + + // Required input not provided + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + ...inputs, + single: [testDataRef2('singleOpt')], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"missing required extension data value(s) 'test1'"`, + ); + + // Wrong value provided + expect(() => + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory) { + return origFactory( + {}, + { + inputs: { + // @ts-expect-error + opt: [testDataRef2('opt')], + // @ts-expect-error + single: [testDataRef1('single'), outputRef({})], + multi: [ + // @ts-expect-error + [testDataRef2('multi1')], + ], + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"extension data 'test2' was provided but not declared"`, + ); + + // Forwarding entire inputs object + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory({}, { inputs }); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }), + ]); + + // Forwarding individual outputs + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi, + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'orig-multi1,orig-multi2', + }), + ]); + + // Overriding based on original input + expect( + factoryOutput( + Blueprint.makeWithOverrides({ + factory(origFactory, { inputs }) { + return origFactory( + {}, + { + inputs: { + single: [ + testDataRef1(`override-${inputs.single.get(testDataRef1)}`), + testDataRef2('new-singleOpt'), + ], + multi: inputs.multi.map(i => [ + testDataRef1(`override-${i.get(testDataRef1)}`), + ]), + }, + }, + ); + }, + }), + mockParentInputs, + ), + ).toEqual([ + outputRef({ + opt: 'none', + single: 'override-orig-single', + singleOpt: 'new-singleOpt', + multi: 'override-orig-multi1,override-orig-multi2', + }), + ]); + }); + it('should allow merging of inputs', () => { const blueprint = createExtensionBlueprint({ kind: 'test-extension', @@ -429,6 +697,43 @@ describe('createExtensionBlueprint', () => { expect(factoryOutput(ext)).toEqual([testDataRef2('foobar')]); }); + it('should reject invalid output from original factory', () => { + const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); + const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); + + expect(() => + factoryOutput( + // @ts-expect-error + createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return [testDataRef2('foo')]; + }, + }).makeWithOverrides({ factory: orig => orig({}) }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"extension data 'test2' was provided but not declared"`, + ); + + expect(() => + factoryOutput( + // @ts-expect-error + createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef1], + factory() { + return []; + }, + }).makeWithOverrides({ factory: orig => orig({}) }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"missing required extension data value(s) 'test1'"`, + ); + }); + it('should allow returning of the parent data container', () => { const testDataRef1 = createExtensionDataRef().with({ id: 'test1' }); const testDataRef2 = createExtensionDataRef().with({ id: 'test2' }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 656e681a2b..40d1284b15 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import { AppNode } from '../apis'; +import { ApiHolder, AppNode } from '../apis'; import { Expand } from '../types'; import { - CreateExtensionOptions, - ExtensionDataContainer, ExtensionDefinition, ResolvedExtensionInputs, VerifyExtensionFactoryOutput, @@ -28,9 +26,16 @@ import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; import { AnyExtensionDataRef, - ExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; +import { + ExtensionDataContainer, + createExtensionDataContainer, +} from './createExtensionDataContainer'; +import { + ResolveInputValueOverrides, + resolveInputOverrides, +} from './resolveInputOverrides'; /** * @public @@ -65,6 +70,7 @@ export type CreateExtensionBlueprintOptions< params: TParams, context: { node: AppNode; + apis: ApiHolder; config: { [key in keyof TConfigSchema]: z.infer>; }; @@ -75,27 +81,51 @@ export type CreateExtensionBlueprintOptions< dataRefs?: TDataRefs; } & VerifyExtensionFactoryOutput; +/** @public */ +export type ExtensionBlueprintParameters = { + kind: string; + namespace?: string; + name?: string; + params?: object; + configInput?: { [K in string]: any }; + config?: { [K in string]: any }; + output?: AnyExtensionDataRef; + inputs?: { + [KName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }; + dataRefs?: { [name in string]: AnyExtensionDataRef }; +}; + /** * @public */ export interface ExtensionBlueprint< - TKind extends string, - TNamespace extends string | undefined, - TName extends string | undefined, - TParams, - UOutput extends AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, - TConfig extends { [key in string]: unknown }, - TConfigInput extends { [key in string]: unknown }, - TDataRefs extends { [name in string]: AnyExtensionDataRef }, + T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters, > { - dataRefs: TDataRefs; + dataRefs: T['dataRefs']; + make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + >(args: { + namespace?: undefined; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; + /** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */ make< TNewNamespace extends string | undefined, TNewName extends string | undefined, @@ -104,16 +134,18 @@ export interface ExtensionBlueprint< name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; - params: TParams; - }): ExtensionDefinition< - TConfig, - TConfigInput, - UOutput, - TInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName - >; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: string | undefined extends TNewNamespace + ? T['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; /** * Creates a new extension from the blueprint. @@ -136,126 +168,75 @@ export interface ExtensionBlueprint< >; }, >(args: { - namespace?: TNewNamespace; + namespace?: undefined; name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TExtraInputs & { - [KName in keyof TInputs]?: `Error: Input '${KName & + [KName in keyof T['inputs']]?: `Error: Input '${KName & string}' is already defined in parent definition`; }; output?: Array; config?: { schema: TExtensionConfigSchema & { - [KName in keyof TConfig]?: `Error: Config key '${KName & + [KName in keyof T['config']]?: `Error: Config key '${KName & string}' is already defined in parent schema`; }; }; factory( originalFactory: ( - params: TParams, + params: T['params'], context?: { - config?: TConfig; - inputs?: Expand>; + config?: T['config']; + inputs?: ResolveInputValueOverrides>; }, - ) => ExtensionDataContainer, + ) => ExtensionDataContainer>, context: { node: AppNode; - config: TConfig & { + apis: ApiHolder; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; }; - inputs: Expand>; + inputs: Expand>; }, ): Iterable & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, UFactoryOutput >; - }): ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - } & TConfig, - z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - }> - > & - TConfigInput, - AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, - TInputs & TExtraInputs, - TKind, - string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, - string | undefined extends TNewName ? TName : TNewName - >; -} - -/** @internal */ -export function createDataContainer( - values: Iterable< - UData extends ExtensionDataRef - ? ExtensionDataValue - : never - >, -): ExtensionDataContainer { - const container = new Map>(); - - for (const output of values) { - container.set(output.id, output); - } - - return { - get(ref) { - return container.get(ref.id)?.value; - }, - [Symbol.iterator]() { - return container.values(); - }, - } as ExtensionDataContainer; -} - -/** - * @internal - */ -class ExtensionBlueprintImpl< - TKind extends string, - TNamespace extends string | undefined, - TName extends string | undefined, - TParams, - UOutput extends AnyExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, - TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, - TDataRefs extends { [name in string]: AnyExtensionDataRef }, -> { - constructor( - private readonly options: CreateExtensionBlueprintOptions< - TKind, - TNamespace, - TName, - TParams, - UOutput, - TInputs, - TConfigSchema, - any, - TDataRefs - >, - ) { - this.dataRefs = options.dataRefs!; - } - - dataRefs: TDataRefs; - - public makeWithOverrides< + }): ExtensionDefinition<{ + config: (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }) & + T['config']; + configInput: (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; + /** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */ + makeWithOverrides< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, @@ -267,138 +248,74 @@ class ExtensionBlueprintImpl< { optional: boolean; singleton: boolean } >; }, - TNewNamespace extends string | undefined = undefined, - TNewName extends string | undefined = undefined, >(args: { - namespace?: TNewNamespace; + namespace: TNewNamespace; name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; - inputs?: TExtraInputs; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; output?: Array; config?: { - schema: TExtensionConfigSchema; + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; }; factory( originalFactory: ( - params: TParams, + params: T['params'], context?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs?: Expand>; + config?: T['config']; + inputs?: ResolveInputValueOverrides>; }, - ) => ExtensionDataContainer, + ) => ExtensionDataContainer>, context: { node: AppNode; - config: { + apis: ApiHolder; + config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType >; - } & { - [key in keyof TConfigSchema]: z.infer>; }; - inputs: Expand>; + inputs: Expand>; }, - ): Iterable; - }): ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput >; - } & { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject< - { - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + }): ExtensionDefinition<{ + config: (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType >; - } & { - [key in keyof TConfigSchema]: ReturnType; - } - > - > - > { - const schema = { - ...this.options.config?.schema, - ...args.config?.schema, - } as TConfigSchema & TExtensionConfigSchema; - - return createExtension({ - kind: this.options.kind, - namespace: args.namespace ?? this.options.namespace, - name: args.name ?? this.options.name, - attachTo: args.attachTo ?? this.options.attachTo, - disabled: args.disabled ?? this.options.disabled, - inputs: { ...args.inputs, ...this.options.inputs }, - output: args.output ?? this.options.output, - config: Object.keys(schema).length === 0 ? undefined : { schema }, - factory: ({ node, config, inputs }) => { - return args.factory( - ( - innerParams: TParams, - innerContext?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; - inputs?: Expand>; - }, - ): ExtensionDataContainer => { - return createDataContainer( - this.options.factory(innerParams, { - node, - config: innerContext?.config ?? config, - inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden - }), - ); - }, - { - node, - config, - inputs, - }, - ); - }, - } as CreateExtensionOptions); - } - - public make< - TNewNamespace extends string | undefined = undefined, - TNewName extends string | undefined = undefined, - >(args: { - namespace?: TNewNamespace; - name?: TNewName; - attachTo?: { id: string; input: string }; - disabled?: boolean; - params: TParams; - }): ExtensionDefinition< - { - [key in keyof TConfigSchema]: z.infer>; - }, - z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType; - }> - > - > { - return createExtension({ - kind: this.options.kind, - namespace: args.namespace ?? this.options.namespace, - name: args.name ?? this.options.name, - attachTo: args.attachTo ?? this.options.attachTo, - disabled: args.disabled ?? this.options.disabled, - inputs: this.options.inputs, - output: this.options.output, - config: this.options.config, - factory: ctx => this.options.factory(args.params, ctx), - } as CreateExtensionOptions); - } + }) & + T['config']; + configInput: (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: string | undefined extends TNewNamespace + ? T['namespace'] + : TNewNamespace; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; } /** @@ -408,7 +325,57 @@ class ExtensionBlueprintImpl< * @public */ export function createExtensionBlueprint< - TParams, + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, +>( + options: CreateExtensionBlueprintOptions< + TKind, + undefined, + TName, + TParams, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: undefined; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema + ? {} + : { [key in keyof TConfigSchema]: z.infer> }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + dataRefs: TDataRefs; +}>; +/** + * @public + * @deprecated the namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. + */ +export function createExtensionBlueprint< + TParams extends object, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -434,44 +401,153 @@ export function createExtensionBlueprint< UFactoryOutput, TDataRefs >, -): ExtensionBlueprint< - TKind, - TNamespace, - TName, - TParams, - UOutput, - string extends keyof TInputs ? {} : TInputs, - string extends keyof TConfigSchema +): ExtensionBlueprint<{ + kind: TKind; + namespace: TNamespace; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema ? {} - : { [key in keyof TConfigSchema]: z.infer> }, - string extends keyof TConfigSchema + : { [key in keyof TConfigSchema]: z.infer> }; + configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >, - TDataRefs -> { - return new ExtensionBlueprintImpl(options) as ExtensionBlueprint< + >; + dataRefs: TDataRefs; +}>; +export function createExtensionBlueprint< + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends string | undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, +>( + options: CreateExtensionBlueprintOptions< TKind, TNamespace, TName, TParams, UOutput, - string extends keyof TInputs ? {} : TInputs, - string extends keyof TConfigSchema + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: TNamespace; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema + ? {} + : { [key in keyof TConfigSchema]: z.infer> }; + configInput: string extends keyof TConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TConfigSchema]: ReturnType; + }> + >; + dataRefs: TDataRefs; +}> { + return { + dataRefs: options.dataRefs, + make(args) { + return createExtension({ + kind: options.kind, + namespace: args.namespace ?? options.namespace, + name: args.name ?? options.name, + attachTo: args.attachTo ?? options.attachTo, + disabled: args.disabled ?? options.disabled, + inputs: options.inputs, + output: options.output as AnyExtensionDataRef[], + config: options.config, + factory: ctx => + options.factory(args.params, ctx) as Iterable< + ExtensionDataValue + >, + }) as ExtensionDefinition; + }, + makeWithOverrides(args) { + return createExtension({ + kind: options.kind, + namespace: args.namespace ?? options.namespace, + name: args.name ?? options.name, + attachTo: args.attachTo ?? options.attachTo, + disabled: args.disabled ?? options.disabled, + inputs: { ...args.inputs, ...options.inputs }, + output: (args.output ?? options.output) as AnyExtensionDataRef[], + config: + options.config || args.config + ? { + schema: { + ...options.config?.schema, + ...args.config?.schema, + }, + } + : undefined, + factory: ({ node, config, inputs, apis }) => { + return args.factory( + (innerParams, innerContext) => { + return createExtensionDataContainer( + options.factory(innerParams, { + apis, + node, + config: (innerContext?.config ?? config) as any, + inputs: resolveInputOverrides( + options.inputs, + inputs, + innerContext?.inputs, + ) as any, + }) as Iterable, + options.output, + ); + }, + { + apis, + node, + config: config as any, + inputs: inputs as any, + }, + ) as Iterable>; + }, + }) as ExtensionDefinition; + }, + } as ExtensionBlueprint<{ + kind: TKind; + namespace: TNamespace; + name: TName; + params: TParams; + output: UOutput; + inputs: string extends keyof TInputs ? {} : TInputs; + config: string extends keyof TConfigSchema ? {} : { [key in keyof TConfigSchema]: z.infer>; - }, - string extends keyof TConfigSchema + }; + configInput: string extends keyof TConfigSchema ? {} : z.input< z.ZodObject<{ [key in keyof TConfigSchema]: ReturnType; }> - >, - TDataRefs - >; + >; + dataRefs: TDataRefs; + }>; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataContainer.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataContainer.ts new file mode 100644 index 0000000000..d79ec70f2f --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataContainer.ts @@ -0,0 +1,88 @@ +/* + * 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 { + AnyExtensionDataRef, + ExtensionDataRef, + ExtensionDataValue, +} from './createExtensionDataRef'; + +/** @public */ +export type ExtensionDataContainer = + Iterable< + UExtensionData extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? IConfig['optional'] extends true + ? never + : ExtensionDataValue + : never + > & { + get( + ref: ExtensionDataRef, + ): UExtensionData extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; + }; + +/** @internal */ +export function createExtensionDataContainer( + values: Iterable< + UData extends ExtensionDataRef + ? ExtensionDataValue + : never + >, + declaredRefs?: ExtensionDataRef[], +): ExtensionDataContainer { + const container = new Map>(); + const verifyRefs = + declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref])); + + for (const output of values) { + if (verifyRefs) { + if (!verifyRefs.delete(output.id)) { + throw new Error( + `extension data '${output.id}' was provided but not declared`, + ); + } + } + container.set(output.id, output); + } + + const remainingRefs = + verifyRefs && + Array.from(verifyRefs.values()).filter(ref => !ref.config.optional); + if (remainingRefs && remainingRefs.length > 0) { + throw new Error( + `missing required extension data value(s) '${remainingRefs + .map(ref => ref.id) + .join(', ')}'`, + ); + } + + return { + get(ref) { + return container.get(ref.id)?.value; + }, + [Symbol.iterator]() { + return container.values(); + }, + } as ExtensionDataContainer; +} diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index c77aa8c394..80413c1688 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -55,7 +55,7 @@ export interface ConfigurableExtensionDataRef< optional(): ConfigurableExtensionDataRef< TData, TId, - TData & { optional: true } + TConfig & { optional: true } >; (t: TData): ExtensionDataValue; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts index e401adc675..084758b793 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts @@ -15,11 +15,7 @@ */ import { createExtensionDataRef } from './createExtensionDataRef'; -import { - ExtensionInput, - LegacyExtensionInput, - createExtensionInput, -} from './createExtensionInput'; +import { ExtensionInput, createExtensionInput } from './createExtensionInput'; const stringDataRef = createExtensionDataRef().with({ id: 'str' }); const numberDataRef = createExtensionDataRef().with({ id: 'num' }); @@ -130,40 +126,4 @@ describe('createExtensionInput', () => { createExtensionInput([stringDataRef, stringDataRef], { singleton: true }), ).toThrow("ExtensionInput may not have duplicate data refs: 'str'"); }); - - describe('old api', () => { - it('should create a regular input', () => { - const input = createExtensionInput({ - str: stringDataRef, - num: numberDataRef, - }); - expect(input).toEqual({ - $$type: '@backstage/ExtensionInput', - extensionData: { str: stringDataRef, num: numberDataRef }, - config: { singleton: false, optional: false }, - }); - - const x1: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x2: LegacyExtensionInput< - { str: typeof numberDataRef; num: typeof stringDataRef }, // switched - { singleton: false; optional: false } - > = input; - // @ts-expect-error - const x3: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: true; optional: false } - > = input; - // @ts-expect-error - const x4: LegacyExtensionInput< - { str: typeof stringDataRef; num: typeof numberDataRef }, - { singleton: false; optional: true } - > = input; - - unused(x1, x2, x3, x4); - }); - }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts index b7efb13e6e..7209e6f647 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts @@ -14,85 +14,33 @@ * limitations under the License. */ -import { AnyExtensionDataMap } from './createExtension'; import { ExtensionDataRef } from './createExtensionDataRef'; /** @public */ export interface ExtensionInput< - TExtensionData extends ExtensionDataRef, + UExtensionData extends ExtensionDataRef, TConfig extends { singleton: boolean; optional: boolean }, > { $$type: '@backstage/ExtensionInput'; - extensionData: Array; + extensionData: Array; config: TConfig; + replaces?: Array<{ id: string; input: string }>; } -/** - * @public - * @deprecated This type will be removed. Use `ExtensionInput` instead. - */ -export interface LegacyExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton: boolean; optional: boolean }, -> { - $$type: '@backstage/ExtensionInput'; - extensionData: TExtensionDataMap; - config: TConfig; -} - -/** - * @public - * @deprecated Use the following form instead: `createExtensionInput([dataRef1, dataRef2])` - */ -export function createExtensionInput< - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton?: boolean; optional?: boolean }, ->( - extensionData: TExtensionDataMap, - config?: TConfig, -): LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } ->; /** @public */ export function createExtensionInput< UExtensionData extends ExtensionDataRef, TConfig extends { singleton?: boolean; optional?: boolean }, >( extensionData: Array, - config?: TConfig, + config?: TConfig & { replaces?: Array<{ id: string; input: string }> }, ): ExtensionInput< UExtensionData, { singleton: TConfig['singleton'] extends true ? true : false; optional: TConfig['optional'] extends true ? true : false; } ->; -export function createExtensionInput< - TExtensionData extends ExtensionDataRef, - TExtensionDataMap extends AnyExtensionDataMap, - TConfig extends { singleton?: boolean; optional?: boolean }, ->( - extensionData: Array | TExtensionDataMap, - config?: TConfig, -): - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > { +> { if (process.env.NODE_ENV !== 'production') { if (Array.isArray(extensionData)) { const seen = new Set(); @@ -124,19 +72,12 @@ export function createExtensionInput< ? true : false, }, - } as - | LegacyExtensionInput< - TExtensionDataMap, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - > - | ExtensionInput< - TExtensionData, - { - singleton: TConfig['singleton'] extends true ? true : false; - optional: TConfig['optional'] extends true ? true : false; - } - >; + replaces: config?.replaces, + } as ExtensionInput< + UExtensionData, + { + singleton: TConfig['singleton'] extends true ? true : false; + optional: TConfig['optional'] extends true ? true : false; + } + >; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index aa00247222..a3595fb168 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -40,22 +40,22 @@ describe('createExtensionOverrides', () => { createExtension({ name: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ namespace: 'b', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), createExtension({ kind: 'k', namespace: 'c', name: 'n', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }), @@ -65,6 +65,7 @@ describe('createExtensionOverrides', () => { "extensions": [ { "$$type": "@backstage/Extension", + "T": undefined, "attachTo": { "id": "app", "input": "apis", @@ -74,12 +75,13 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "a", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", + "T": undefined, "attachTo": { "id": "app", "input": "apis", @@ -89,12 +91,13 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "b", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, { "$$type": "@backstage/Extension", + "T": undefined, "attachTo": { "id": "app", "input": "apis", @@ -104,9 +107,9 @@ describe('createExtensionOverrides', () => { "factory": [Function], "id": "k:c/n", "inputs": {}, - "output": {}, + "output": [], "toString": [Function], - "version": "v1", + "version": "v2", }, ], "featureFlags": [], @@ -122,8 +125,8 @@ describe('createExtensionOverrides', () => { createExtension({ namespace: 'a', attachTo: { id: 'app', input: 'apis' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], }), ], }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts index 75c43a36d2..887f9b8dab 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts @@ -21,9 +21,12 @@ import { } from './resolveExtensionDefinition'; import { ExtensionOverrides, FeatureFlagConfig } from './types'; -/** @public */ +/** + * @deprecated Use {@link createFrontendModule} instead. + * @public + */ export interface ExtensionOverridesOptions { - extensions: ExtensionDefinition[]; + extensions: ExtensionDefinition[]; featureFlags?: FeatureFlagConfig[]; } @@ -34,7 +37,10 @@ export interface InternalExtensionOverrides extends ExtensionOverrides { readonly featureFlags: FeatureFlagConfig[]; } -/** @public */ +/** + * @deprecated Use {@link createFrontendModule} instead. + * @public + */ export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides { diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts new file mode 100644 index 0000000000..d895f6de0f --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtension } from './createExtension'; +import { createFrontendModule } from './createFrontendModule'; + +describe('createFrontendModule', () => { + it('should create a frontend module', () => { + expect( + createFrontendModule({ + pluginId: 'test', + extensions: [ + createExtension({ + kind: 'route', + name: 'test', + output: [], + attachTo: { id: 'ignored', input: 'ignored' }, + factory: () => [], + }), + ], + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/FrontendModule", + "extensions": [ + { + "$$type": "@backstage/Extension", + "T": undefined, + "attachTo": { + "id": "ignored", + "input": "ignored", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "id": "route:test/test", + "inputs": {}, + "output": [], + "toString": [Function], + "version": "v2", + }, + ], + "featureFlags": [], + "pluginId": "test", + "toString": [Function], + "version": "v1", + } + `); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts new file mode 100644 index 0000000000..aacc34ccf0 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -0,0 +1,127 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ExtensionDefinition, + InternalExtensionDefinition, + toInternalExtensionDefinition, +} from './createExtension'; +import { + Extension, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; +import { FeatureFlagConfig } from './types'; + +/** @public */ +export interface CreateFrontendModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + pluginId: TPluginId; + extensions?: TExtensions; + featureFlags?: FeatureFlagConfig[]; +} + +/** @public */ +export interface FrontendModule { + readonly $$type: '@backstage/FrontendModule'; + readonly pluginId: string; +} + +/** @internal */ +export interface InternalFrontendModule extends FrontendModule { + readonly version: 'v1'; + readonly extensions: Extension[]; + readonly featureFlags: FeatureFlagConfig[]; +} + +/** @public */ +export function createFrontendModule< + TId extends string, + TExtensions extends readonly ExtensionDefinition[] = [], +>(options: CreateFrontendModuleOptions): FrontendModule { + const { pluginId } = options; + + const extensions = new Array>(); + const extensionDefinitionsById = new Map< + string, + InternalExtensionDefinition + >(); + + for (const def of options.extensions ?? []) { + const internal = toInternalExtensionDefinition(def); + const ext = resolveExtensionDefinition(def, { namespace: pluginId }); + extensions.push(ext); + extensionDefinitionsById.set(ext.id, { + ...internal, + namespace: pluginId, + }); + } + + if (extensions.length !== extensionDefinitionsById.size) { + const extensionIds = extensions.map(e => e.id); + const duplicates = Array.from( + new Set( + extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), + ), + ); + // TODO(Rugvip): This could provide some more information about the kind + name of the extensions + throw new Error( + `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join( + ', ', + )}`, + ); + } + + return { + $$type: '@backstage/FrontendModule', + version: 'v1', + pluginId, + featureFlags: options.featureFlags ?? [], + extensions, + toString() { + return `Module{pluginId=${pluginId}}`; + }, + } as InternalFrontendModule; +} + +/** @internal */ +export function isInternalFrontendModule(opaque: { + $$type: string; +}): opaque is InternalFrontendModule { + if (opaque.$$type === '@backstage/FrontendModule') { + // Make sure we throw if invalid + toInternalFrontendModule(opaque as FrontendModule); + return true; + } + return false; +} + +/** @internal */ +export function toInternalFrontendModule( + plugin: FrontendModule, +): InternalFrontendModule { + const internal = plugin as InternalFrontendModule; + if (internal.$$type !== '@backstage/FrontendModule') { + throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid plugin instance, bad version '${internal.version}'`, + ); + } + return internal; +} diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts similarity index 56% rename from packages/frontend-plugin-api/src/wiring/createPlugin.test.ts rename to packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 76cc030be3..c461d6fb20 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -17,15 +17,13 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { screen } from '@testing-library/react'; -import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createPlugin } from './createPlugin'; +import { FrontendPlugin, createFrontendPlugin } from './createFrontendPlugin'; import { JsonObject } from '@backstage/types'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { coreExtensionData } from './coreExtensionData'; import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; import { createExtensionInput } from './createExtensionInput'; -import { BackstagePlugin } from './types'; const nameExtensionDataRef = createExtensionDataRef().with({ id: 'name', @@ -43,14 +41,14 @@ const Extension1 = createExtension({ const Extension2 = createExtension({ name: '2', attachTo: { id: 'test/output', input: 'names' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('extension-2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('extension-2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -58,45 +56,45 @@ const Extension3 = createExtension({ name: '3', attachTo: { id: 'test/output', input: 'names' }, inputs: { - addons: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - name: nameExtensionDataRef, + addons: createExtensionInput([nameExtensionDataRef]), }, + output: [nameExtensionDataRef], factory({ inputs }) { - return { - name: `extension-3:${inputs.addons.map(n => n.output.name).join('-')}`, - }; + return [ + nameExtensionDataRef( + `extension-3:${inputs.addons + .map(n => n.get(nameExtensionDataRef)) + .join('-')}`, + ), + ]; }, }); const Child = createExtension({ name: 'child', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); const Child2 = createExtension({ name: 'child2', attachTo: { id: 'test/3', input: 'addons' }, - output: { - name: nameExtensionDataRef, + output: [nameExtensionDataRef], + config: { + schema: { + name: z => z.string().default('child2'), + }, }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('child2') }), - ), factory({ config }) { - return { name: config.name }; + return [nameExtensionDataRef(config.name)]; }, }); @@ -104,19 +102,19 @@ const outputExtension = createExtension({ name: 'output', attachTo: { id: 'app', input: 'root' }, inputs: { - names: createExtensionInput({ - name: nameExtensionDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, + names: createExtensionInput([nameExtensionDataRef]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { - return { - element: React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.output.name).join(', ')}`, - ]), - }; + return [ + coreExtensionData.reactElement( + React.createElement('span', {}, [ + `Names: ${inputs.names + .map(n => n.get(nameExtensionDataRef)) + .join(', ')}`, + ]), + ), + ]; }, }); @@ -124,31 +122,53 @@ function createTestAppRoot({ features, config = {}, }: { - features: BackstagePlugin[]; + features: FrontendPlugin[]; config: JsonObject; }) { return createApp({ - features, + features: [...features], configLoader: async () => ({ config: new MockConfigApi(config) }), }).createRoot(); } -describe('createPlugin', () => { +describe('createFrontendPlugin', () => { it('should create an empty plugin', () => { - const plugin = createPlugin({ id: 'test' }); + const plugin = createFrontendPlugin({ id: 'test' }); expect(plugin).toBeDefined(); expect(String(plugin)).toBe('Plugin{id=test}'); }); it('should create a plugin with extension instances', async () => { - const plugin = createPlugin({ + const plugin = createFrontendPlugin({ id: 'test', extensions: [Extension1, Extension2, outputExtension], }); expect(plugin).toBeDefined(); - expect(plugin.getExtension('test/1')).toBe(Extension1); + expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "test/output", + "input": "names", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": undefined, + "name": "1", + "namespace": "test", + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); // @ts-expect-error expect(plugin.getExtension('nonexistent')).toBeUndefined(); @@ -165,7 +185,7 @@ describe('createPlugin', () => { }); it('should create a plugin with nested extension instances', async () => { - const plugin = createPlugin({ + const plugin = createFrontendPlugin({ id: 'test', extensions: [Extension1, Extension2, Extension3, Child, outputExtension], }); @@ -197,7 +217,7 @@ describe('createPlugin', () => { }); it('should create a plugin with nested extension instances and multiple children', async () => { - const plugin = createPlugin({ + const plugin = createFrontendPlugin({ id: 'test', extensions: [ Extension1, @@ -230,14 +250,14 @@ describe('createPlugin', () => { it('should throw on duplicate extensions', async () => { expect(() => - createPlugin({ + createFrontendPlugin({ id: 'test', extensions: [Extension1, Extension1], }), ).toThrow("Plugin 'test' provided duplicate extensions: test/1"); expect(() => - createPlugin({ + createFrontendPlugin({ id: 'test', extensions: [ Extension1, @@ -250,4 +270,69 @@ describe('createPlugin', () => { }), ).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3"); }); + + describe('overrides', () => { + it('should return a plugin instance with the correct namespace', () => { + const plugin = createFrontendPlugin({ + id: 'test', + extensions: [Extension1, Extension2], + }); + + expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "test/output", + "input": "names", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": undefined, + "name": "1", + "namespace": "test", + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should allow overriding extensions that have a matching ID, while keeping old extensions that do not have overlapping IDs', async () => { + const plugin = createFrontendPlugin({ + id: 'test', + extensions: [Extension1, Extension2, outputExtension], + }); + + await renderWithEffects( + createTestAppRoot({ + features: [ + plugin.withOverrides({ + extensions: [ + plugin.getExtension('test/1').override({ + factory() { + return [nameExtensionDataRef('overridden')]; + }, + }), + ], + }), + ], + config: { + app: { + extensions: [{ 'app/root': false }], + }, + }, + }), + ); + + await expect( + screen.findByText('Names: extension-2, overridden'), + ).resolves.toBeInTheDocument(); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts new file mode 100644 index 0000000000..03c843543d --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -0,0 +1,199 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ExtensionDefinition, + InternalExtensionDefinition, + toInternalExtensionDefinition, +} from './createExtension'; +import { + Extension, + ResolveExtensionId, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; +import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types'; + +/** @public */ +export interface FrontendPlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, +> { + readonly $$type: '@backstage/FrontendPlugin'; + readonly id: string; + readonly routes: TRoutes; + readonly externalRoutes: TExternalRoutes; + getExtension(id: TId): TExtensionMap[TId]; + withOverrides(options: { + extensions: Array; + }): FrontendPlugin; +} + +/** + * @public + * @deprecated Use {@link FrontendPlugin} instead. + */ +export type BackstagePlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, +> = FrontendPlugin; +/** @public */ +export interface PluginOptions< + TId extends string, + TRoutes extends AnyRoutes, + TExternalRoutes extends AnyExternalRoutes, + TExtensions extends readonly ExtensionDefinition[], +> { + id: TId; + routes?: TRoutes; + externalRoutes?: TExternalRoutes; + extensions?: TExtensions; + featureFlags?: FeatureFlagConfig[]; +} + +/** @public */ +export interface InternalFrontendPlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, +> extends FrontendPlugin { + readonly version: 'v1'; + readonly extensions: Extension[]; + readonly featureFlags: FeatureFlagConfig[]; +} + +/** @public */ +export function createFrontendPlugin< + TId extends string, + TRoutes extends AnyRoutes = {}, + TExternalRoutes extends AnyExternalRoutes = {}, + TExtensions extends readonly ExtensionDefinition[] = [], +>( + options: PluginOptions, +): FrontendPlugin< + TRoutes, + TExternalRoutes, + { + [KExtension in TExtensions[number] as ResolveExtensionId< + KExtension, + TId + >]: KExtension; + } +> { + const extensions = new Array>(); + const extensionDefinitionsById = new Map< + string, + InternalExtensionDefinition + >(); + + for (const def of options.extensions ?? []) { + const internal = toInternalExtensionDefinition(def); + const ext = resolveExtensionDefinition(def, { namespace: options.id }); + extensions.push(ext); + extensionDefinitionsById.set(ext.id, { + ...internal, + namespace: options.id, + }); + } + + if (extensions.length !== extensionDefinitionsById.size) { + const extensionIds = extensions.map(e => e.id); + const duplicates = Array.from( + new Set( + extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), + ), + ); + // TODO(Rugvip): This could provide some more information about the kind + name of the extensions + throw new Error( + `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join( + ', ', + )}`, + ); + } + + return { + $$type: '@backstage/FrontendPlugin', + version: 'v1', + id: options.id, + routes: options.routes ?? ({} as TRoutes), + externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), + featureFlags: options.featureFlags ?? [], + extensions, + getExtension(id) { + return extensionDefinitionsById.get(id); + }, + toString() { + return `Plugin{id=${options.id}}`; + }, + withOverrides(overrides) { + const overriddenExtensionIds = new Set( + overrides.extensions.map( + e => resolveExtensionDefinition(e, { namespace: options.id }).id, + ), + ); + const nonOverriddenExtensions = (options.extensions ?? []).filter( + e => + !overriddenExtensionIds.has( + resolveExtensionDefinition(e, { namespace: options.id }).id, + ), + ); + return createFrontendPlugin({ + ...options, + extensions: [...nonOverriddenExtensions, ...overrides.extensions], + }); + }, + } as InternalFrontendPlugin; +} + +/** @internal */ +export function isInternalFrontendPlugin(opaque: { + $$type: string; +}): opaque is InternalFrontendPlugin { + if ( + opaque.$$type === '@backstage/FrontendPlugin' || + opaque.$$type === '@backstage/BackstagePlugin' + ) { + // Make sure we throw if invalid + toInternalFrontendPlugin(opaque as FrontendPlugin); + return true; + } + return false; +} + +/** @internal */ +export function toInternalFrontendPlugin( + plugin: FrontendPlugin, +): InternalFrontendPlugin { + const internal = plugin as InternalFrontendPlugin; + if ( + internal.$$type !== '@backstage/FrontendPlugin' && + internal.$$type !== '@backstage/BackstagePlugin' + ) { + throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid plugin instance, bad version '${internal.version}'`, + ); + } + return internal; +} + +/** + * @public + * @deprecated Use {@link createFrontendPlugin} instead. + */ +export const createPlugin = createFrontendPlugin; diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts deleted file mode 100644 index 1d2a12ffe2..0000000000 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ /dev/null @@ -1,130 +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 { ExtensionDefinition } from './createExtension'; -import { - Extension, - ResolveExtensionId, - resolveExtensionDefinition, -} from './resolveExtensionDefinition'; -import { - AnyExternalRoutes, - AnyRoutes, - BackstagePlugin, - FeatureFlagConfig, -} from './types'; - -/** @public */ -export interface PluginOptions< - TId extends string, - TRoutes extends AnyRoutes, - TExternalRoutes extends AnyExternalRoutes, - TExtensions extends readonly ExtensionDefinition[], -> { - id: TId; - routes?: TRoutes; - externalRoutes?: TExternalRoutes; - extensions?: TExtensions; - featureFlags?: FeatureFlagConfig[]; -} - -/** @public */ -export interface InternalBackstagePlugin< - TRoutes extends AnyRoutes = AnyRoutes, - TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, -> extends BackstagePlugin { - readonly version: 'v1'; - readonly extensions: Extension[]; - readonly featureFlags: FeatureFlagConfig[]; -} - -/** @public */ -export function createPlugin< - TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, - TExtensions extends readonly ExtensionDefinition[] = [], ->( - options: PluginOptions, -): BackstagePlugin< - TRoutes, - TExternalRoutes, - { - [KExtension in TExtensions[number] as ResolveExtensionId< - KExtension, - TId - >]: KExtension; - } -> { - const extensions = new Array>(); - const extensionDefinitionsById = new Map< - string, - ExtensionDefinition - >(); - - for (const def of options.extensions ?? []) { - const ext = resolveExtensionDefinition(def, { namespace: options.id }); - extensions.push(ext); - extensionDefinitionsById.set(ext.id, def); - } - - if (extensions.length !== extensionDefinitionsById.size) { - const extensionIds = extensions.map(e => e.id); - const duplicates = Array.from( - new Set( - extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), - ), - ); - // TODO(Rugvip): This could provide some more information about the kind + name of the extensions - throw new Error( - `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join( - ', ', - )}`, - ); - } - - return { - $$type: '@backstage/BackstagePlugin', - version: 'v1', - id: options.id, - routes: options.routes ?? ({} as TRoutes), - externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), - featureFlags: options.featureFlags ?? [], - extensions, - getExtension(id) { - return extensionDefinitionsById.get(id); - }, - toString() { - return `Plugin{id=${options.id}}`; - }, - } as InternalBackstagePlugin; -} - -/** @internal */ -export function toInternalBackstagePlugin( - plugin: BackstagePlugin, -): InternalBackstagePlugin { - const internal = plugin as InternalBackstagePlugin; - if (internal.$$type !== '@backstage/BackstagePlugin') { - throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); - } - if (internal.version !== 'v1') { - throw new Error( - `Invalid plugin instance, bad version '${internal.version}'`, - ); - } - return internal; -} diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index a17dfedc16..f8ad362bac 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -17,21 +17,17 @@ export { coreExtensionData } from './coreExtensionData'; export { createExtension, - type ExtensionDataContainer, type ExtensionDefinition, + type ExtensionDefinitionParameters, type CreateExtensionOptions, - type ExtensionDataValues, type ResolvedExtensionInput, type ResolvedExtensionInputs, - type LegacyCreateExtensionOptions, - type AnyExtensionInputMap, - type AnyExtensionDataMap, } from './createExtension'; export { createExtensionInput, type ExtensionInput, - type LegacyExtensionInput, } from './createExtensionInput'; +export { type ExtensionDataContainer } from './createExtensionDataContainer'; export { createExtensionDataRef, type AnyExtensionDataRef, @@ -40,7 +36,18 @@ export { type ExtensionDataValue, type ConfigurableExtensionDataRef, } from './createExtensionDataRef'; -export { createPlugin, type PluginOptions } from './createPlugin'; +export { + createPlugin, + createFrontendPlugin, + type FrontendPlugin, + type BackstagePlugin, + type PluginOptions, +} from './createFrontendPlugin'; +export { + createFrontendModule, + type FrontendModule, + type CreateFrontendModuleOptions, +} from './createFrontendModule'; export { createExtensionOverrides, type ExtensionOverridesOptions, @@ -49,7 +56,6 @@ export { type Extension } from './resolveExtensionDefinition'; export { type AnyRoutes, type AnyExternalRoutes, - type BackstagePlugin, type ExtensionOverrides, type FeatureFlagConfig, type FrontendFeature, @@ -57,5 +63,7 @@ export { export { type CreateExtensionBlueprintOptions, type ExtensionBlueprint, + type ExtensionBlueprintParameters, createExtensionBlueprint, } from './createExtensionBlueprint'; +export { type ResolveInputValueOverrides } from './resolveInputOverrides'; diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index 5a42e9100e..09c7b9e9a9 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -23,10 +23,11 @@ import { describe('resolveExtensionDefinition', () => { const baseDef = { $$type: '@backstage/ExtensionDefinition', + T: undefined as any, version: 'v2', attachTo: { id: '', input: '' }, disabled: false, - override: () => ({} as ExtensionDefinition), + override: () => ({} as ExtensionDefinition), }; it.each([ @@ -39,7 +40,7 @@ describe('resolveExtensionDefinition', () => { const resolved = resolveExtensionDefinition({ ...baseDef, ...definition, - } as ExtensionDefinition); + } as ExtensionDefinition); expect(resolved.id).toBe(expected); expect(String(resolved)).toBe(`Extension{id=${expected}}`); }); @@ -49,12 +50,12 @@ describe('resolveExtensionDefinition', () => { resolveExtensionDefinition({ ...baseDef, kind: 'k', - } as ExtensionDefinition), + } as ExtensionDefinition), ).toThrow( 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined', ); expect(() => - resolveExtensionDefinition(baseDef as ExtensionDefinition), + resolveExtensionDefinition(baseDef as ExtensionDefinition), ).toThrow( 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined', ); @@ -64,10 +65,11 @@ describe('resolveExtensionDefinition', () => { describe('old resolveExtensionDefinition', () => { const baseDef = { $$type: '@backstage/ExtensionDefinition', + T: undefined as any, version: 'v1', attachTo: { id: '', input: '' }, disabled: false, - override: () => ({} as ExtensionDefinition), + override: () => ({} as ExtensionDefinition), }; it.each([ @@ -80,7 +82,7 @@ describe('old resolveExtensionDefinition', () => { const resolved = resolveExtensionDefinition({ ...baseDef, ...definition, - } as ExtensionDefinition); + } as ExtensionDefinition); expect(resolved.id).toBe(expected); expect(String(resolved)).toBe(`Extension{id=${expected}}`); }); @@ -90,12 +92,12 @@ describe('old resolveExtensionDefinition', () => { resolveExtensionDefinition({ ...baseDef, kind: 'k', - } as ExtensionDefinition), + } as ExtensionDefinition), ).toThrow( 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined', ); expect(() => - resolveExtensionDefinition(baseDef as ExtensionDefinition), + resolveExtensionDefinition(baseDef as ExtensionDefinition), ).toThrow( 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined', ); @@ -108,7 +110,12 @@ describe('ResolveExtensionId', () => { TKind extends string | undefined, TNamespace extends string | undefined, TName extends string | undefined, - > = ExtensionDefinition; + > = ExtensionDefinition<{ + kind: TKind; + namespace: TNamespace; + name: TName; + output: any; + }>; const id1: 'k:ns' = {} as ResolveExtensionId< NamedExtension<'k', 'ns', undefined>, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 35bf9c6b05..5a631819d7 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { AppNode } from '../apis'; +import { ApiHolder, AppNode } from '../apis'; import { - AnyExtensionDataMap, - AnyExtensionInputMap, - ExtensionDataValues, ExtensionDefinition, + ExtensionDefinitionParameters, ResolvedExtensionInputs, toInternalExtensionDefinition, } from './createExtension'; @@ -47,13 +45,28 @@ export type InternalExtension = Extension< ( | { readonly version: 'v1'; - readonly inputs: AnyExtensionInputMap; - readonly output: AnyExtensionDataMap; - factory(options: { + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; + factory(context: { + apis: ApiHolder; node: AppNode; config: TConfig; - inputs: ResolvedExtensionInputs; - }): ExtensionDataValues; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; } | { readonly version: 'v2'; @@ -65,6 +78,7 @@ export type InternalExtension = Extension< }; readonly output: Array; factory(options: { + apis: ApiHolder; node: AppNode; config: TConfig; inputs: ResolvedExtensionInputs<{ @@ -96,17 +110,13 @@ export function toInternalExtension( /** @ignore */ export type ResolveExtensionId< - TExtension extends ExtensionDefinition, + TExtension extends ExtensionDefinition, TDefaultNamespace extends string | undefined, -> = TExtension extends ExtensionDefinition< - any, - any, - any, - any, - infer IKind, - infer INamespace, - infer IName -> +> = TExtension extends ExtensionDefinition<{ + kind: infer IKind extends string | undefined; + namespace: infer INamespace extends string | undefined; + name: infer IName extends string | undefined; +}> ? [string | undefined] extends [IKind | INamespace | IName] ? never : ( @@ -125,10 +135,12 @@ export type ResolveExtensionId< : never; /** @internal */ -export function resolveExtensionDefinition( - definition: ExtensionDefinition, +export function resolveExtensionDefinition< + T extends ExtensionDefinitionParameters, +>( + definition: ExtensionDefinition, context?: { namespace?: string }, -): Extension { +): Extension { const internalDefinition = toInternalExtensionDefinition(definition); const { name, @@ -137,6 +149,7 @@ export function resolveExtensionDefinition( override: _skip2, ...rest } = internalDefinition; + const namespace = internalDefinition.namespace ?? context?.namespace; const namePart = @@ -157,5 +170,5 @@ export function resolveExtensionDefinition( toString() { return `Extension{id=${id}}`; }, - } as InternalExtension; + } as InternalExtension & Object; } diff --git a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts new file mode 100644 index 0000000000..cf201779e0 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts @@ -0,0 +1,179 @@ +/* + * 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 { AppNode } from '../apis'; +import { Expand } from '../types'; +import { ResolvedExtensionInput } from './createExtension'; +import { + ExtensionDataContainer, + createExtensionDataContainer, +} from './createExtensionDataContainer'; +import { + AnyExtensionDataRef, + ExtensionDataRefToValue, + ExtensionDataValue, +} from './createExtensionDataRef'; +import { ExtensionInput } from './createExtensionInput'; + +/** @public */ +export type ResolveInputValueOverrides< + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + } = { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, +> = Expand< + { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? never + : KName + : never]: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { optional: boolean; singleton: infer ISingleton extends boolean } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } & { + [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< + any, + { + optional: infer IOptional extends boolean; + singleton: boolean; + } + > + ? IOptional extends true + ? KName + : never + : never]?: TInputs[KName] extends ExtensionInput< + infer IDataRefs, + { optional: boolean; singleton: infer ISingleton extends boolean } + > + ? ISingleton extends true + ? Iterable> + : Array>> + : never; + } +>; + +function expectArray(value: T | T[]): T[] { + return value as T[]; +} +function expectItem(value: T | T[]): T { + return value as T; +} + +/** @internal */ +export function resolveInputOverrides( + declaredInputs?: { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + inputs?: { + [KName in string]?: + | ({ node: AppNode } & ExtensionDataContainer) + | Array<{ node: AppNode } & ExtensionDataContainer>; + }, + inputOverrides?: ResolveInputValueOverrides, +) { + if (!declaredInputs || !inputs || !inputOverrides) { + return inputs; + } + + const newInputs: typeof inputs = {}; + for (const name in declaredInputs) { + if (!Object.hasOwn(declaredInputs, name)) { + continue; + } + const declaredInput = declaredInputs[name]; + const providedData = inputOverrides[name]; + if (declaredInput.config.singleton) { + const originalInput = expectItem(inputs[name]); + if (providedData) { + const providedContainer = createExtensionDataContainer( + providedData as Iterable>, + declaredInput.extensionData, + ); + if (!originalInput) { + throw new Error( + `attempted to override data of input '${name}' but it is not present in the original inputs`, + ); + } + newInputs[name] = Object.assign(providedContainer, { + node: (originalInput as ResolvedExtensionInput).node, + }) as any; + } + } else { + const originalInput = expectArray(inputs[name]); + if (!Array.isArray(providedData)) { + throw new Error( + `override data provided for input '${name}' must be an array`, + ); + } + + // Regular inputs can be overridden in two different ways: + // 1) Forward a subset of the original inputs in a new order + // 2) Provide new data for each original input + + // First check if all inputs are being removed + if (providedData.length === 0) { + newInputs[name] = []; + } else { + // Check how many of the provided data items have a node property, i.e. is a forwarded input + const withNodesCount = providedData.filter(d => 'node' in d).length; + if (withNodesCount === 0) { + if (originalInput.length !== providedData.length) { + throw new Error( + `override data provided for input '${name}' must match the length of the original inputs`, + ); + } + newInputs[name] = providedData.map((data, i) => { + const providedContainer = createExtensionDataContainer( + data as Iterable>, + declaredInput.extensionData, + ); + return Object.assign(providedContainer, { + node: (originalInput[i] as ResolvedExtensionInput).node, + }) as any; + }); + } else if (withNodesCount === providedData.length) { + newInputs[name] = providedData as any; + } else { + throw new Error( + `override data for input '${name}' may not mix forwarded inputs with data overrides`, + ); + } + } + } + } + return newInputs; +} diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index b1afd4e7ea..b52bbf446c 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -16,6 +16,7 @@ import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; import { ExtensionDefinition } from './createExtension'; +import { FrontendPlugin } from './createFrontendPlugin'; /** * Feature flag configuration. @@ -35,28 +36,18 @@ export type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; /** @public */ export type ExtensionMap< - TExtensionMap extends { [id in string]: ExtensionDefinition }, + TExtensionMap extends { [id in string]: ExtensionDefinition }, > = { get(id: TId): TExtensionMap[TId]; }; -/** @public */ -export interface BackstagePlugin< - TRoutes extends AnyRoutes = AnyRoutes, - TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, - TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, -> { - readonly $$type: '@backstage/BackstagePlugin'; - readonly id: string; - readonly routes: TRoutes; - readonly externalRoutes: TExternalRoutes; - getExtension(id: TId): TExtensionMap[TId]; -} - /** @public */ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; } -/** @public */ -export type FrontendFeature = BackstagePlugin | ExtensionOverrides; +/** + * @public + * @deprecated import from {@link @backstage/frontend-app-api#FrontendFeature} instead + */ +export type FrontendFeature = FrontendPlugin | ExtensionOverrides; diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 4ba38f5065..fdc40b60a8 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,156 @@ # @backstage/frontend-test-utils +## 0.2.0-next.1 + +### Patch Changes + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/types@1.1.1 + +## 0.2.0-next.0 + +### Minor Changes + +- 5446061: Removed support for testing "v1" extensions, where outputs are defined as an object rather than an array. +- e6e488c: **BREAKING**: The deprecated `.render()` method has been removed from the extension tester. + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 4a66456: Internal update to add support for passing an `ApiRegistry` when creating the node tree +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. +- f6d1874: Added the ability to provide additional `extensions` and `features` to `renderInTestApp` +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.0 + - @backstage/plugin-app@0.1.0-next.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 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 + +## 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 + ## 0.1.12-next.2 ### Patch Changes diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index 3aa9069219..10b84b0981 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -7,11 +7,14 @@ import { AnalyticsApi } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppNodeInstance } from '@backstage/frontend-plugin-api'; import { ErrorWithContext } from '@backstage/test-utils'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-app-api'; import { JsonObject } from '@backstage/types'; import { MockConfigApi } from '@backstage/test-utils'; import { MockErrorApi } from '@backstage/test-utils'; @@ -30,20 +33,26 @@ import { TestApiRegistry } from '@backstage/test-utils'; import { withLogCollector } from '@backstage/test-utils'; // @public (undocumented) -export function createExtensionTester( - subject: ExtensionDefinition, +export function createExtensionTester( + subject: ExtensionDefinition, options?: { - config?: TConfig; + config?: T['configInput']; }, -): ExtensionTester; +): ExtensionTester>; export { ErrorWithContext }; // @public (undocumented) -export class ExtensionQuery { +export class ExtensionQuery { constructor(node: AppNode); // (undocumented) - data(ref: ExtensionDataRef): T | undefined; + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; // (undocumented) get instance(): AppNodeInstance; // (undocumented) @@ -51,20 +60,28 @@ export class ExtensionQuery { } // @public (undocumented) -export class ExtensionTester { +export class ExtensionTester { // (undocumented) - add( - extension: ExtensionDefinition, + add( + extension: ExtensionDefinition, options?: { - config?: TConfigInput; + config?: T['configInput']; }, - ): ExtensionTester; + ): ExtensionTester; // (undocumented) - data(ref: ExtensionDataRef): T | undefined; + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never; // (undocumented) - query(id: string | ExtensionDefinition): ExtensionQuery; + query( + extension: ExtensionDefinition, + ): ExtensionQuery>; // (undocumented) - render(options?: { config?: JsonObject }): RenderResult; + reactElement(): JSX.Element; } // @public @@ -117,6 +134,9 @@ export type TestAppOptions = { mountedRoutes?: { [path: string]: RouteRef; }; + config?: JsonObject; + extensions?: ExtensionDefinition[]; + features?: FrontendFeature[]; }; export { withLogCollector }; diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index e8152cfdda..988ce42822 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.12-next.2", + "version": "0.2.0-next.1", "backstage": { "role": "web-library" }, @@ -34,6 +34,7 @@ "@backstage/config": "workspace:^", "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-app": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/types": "workspace:^" }, diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 889c815fc7..a6265ae3ac 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -14,23 +14,13 @@ * limitations under the License. */ -import React, { useCallback } from 'react'; -import { Link } from 'react-router-dom'; -import { fireEvent, screen, waitFor } from '@testing-library/react'; +import React from 'react'; import { - analyticsApiRef, - configApiRef, coreExtensionData, - createApiExtension, - createApiFactory, createExtension, createExtensionDataRef, createExtensionInput, - createSchemaFromZod, - useAnalytics, - useApi, } from '@backstage/frontend-plugin-api'; -import { MockAnalyticsApi } from '../apis'; import { createExtensionTester } from './createExtensionTester'; const stringDataRef = createExtensionDataRef().with({ @@ -38,330 +28,129 @@ const stringDataRef = createExtensionDataRef().with({ }); describe('createExtensionTester', () => { - const defaultDefinition = { - namespace: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
test
}), - }; - - it('should render a simple extension', async () => { - const extension = createExtension(defaultDefinition); - const tester = createExtensionTester(extension); - tester.render(); - await expect(screen.findByText('test')).resolves.toBeInTheDocument(); - }); - - it('should render an extension even if disabled by default', async () => { - const extension = createExtension({ - ...defaultDefinition, - disabled: true, - }); - const tester = createExtensionTester(extension); - tester.render(); - await expect(screen.findByText('test')).resolves.toBeInTheDocument(); - }); - - it("should fail to render an extension that doesn't output a react element", async () => { - const extension = createExtension({ - ...defaultDefinition, - output: { path: coreExtensionData.routePath }, - factory: () => ({ path: '/foo' }), - }); - const tester = createExtensionTester(extension); - expect(() => tester.render()).toThrowErrorMatchingInlineSnapshot( - `"Failed to instantiate extension 'app/routes', extension 'test' could not be attached because its output data ('core.routing.path') does not match what the input 'routes' requires ('core.routing.path', 'core.reactElement')"`, - ); - }); - - it('should render multiple extensions', async () => { - const indexPageExtension = createExtension({ - ...defaultDefinition, - factory: () => ({ - element: ( -
- Index page See details -
- ), - }), - }); - const detailsPageExtension = createExtension({ - ...defaultDefinition, - name: 'details', - attachTo: { id: 'app/routes', input: 'routes' }, - output: { - path: coreExtensionData.routePath, - element: coreExtensionData.reactElement, - }, - factory: () => ({ path: '/details', element:
Details page
}), - }); - - const tester = createExtensionTester(indexPageExtension); - tester.add(detailsPageExtension); - tester.render(); - - await expect(screen.findByText('Index page')).resolves.toBeInTheDocument(); - - fireEvent.click(screen.getByRole('link', { name: 'See details' })); - - await expect( - screen.findByText('Details page'), - ).resolves.toBeInTheDocument(); - }); - - it('should accepts a custom config', async () => { - const indexPageExtension = createExtension({ - ...defaultDefinition, - configSchema: createSchemaFromZod(z => - z.object({ title: z.string().optional() }), - ), - factory: ({ config }) => { - const Component = () => { - const configApi = useApi(configApiRef); - const appTitle = configApi.getOptionalString('app.title'); - return ( -
-

{appTitle ?? 'Backstafe app'}

-

{config.title ?? 'Index page'}

- See details -
- ); - }; - return { - element: , - }; - }, - }); - - const detailsPageExtension = createExtension({ - ...defaultDefinition, - name: 'details', - attachTo: { id: 'app/routes', input: 'routes' }, - configSchema: createSchemaFromZod(z => - z.object({ title: z.string().optional() }), - ), - output: { - path: coreExtensionData.routePath, - element: coreExtensionData.reactElement, - }, - factory: ({ config }) => ({ - path: '/details', - element:
{config.title ?? 'Details page'}
, - }), - }); - - const tester = createExtensionTester(indexPageExtension, { - config: { title: 'Custom index' }, - }); - - tester.add(detailsPageExtension, { - config: { title: 'Custom details' }, - }); - - tester.render({ - config: { - app: { - title: 'Custom app', - }, - }, - }); - - await expect( - screen.findByRole('heading', { name: 'Custom app' }), - ).resolves.toBeInTheDocument(); - - await expect( - screen.findByRole('heading', { name: 'Custom index' }), - ).resolves.toBeInTheDocument(); - - fireEvent.click(screen.getByRole('link', { name: 'See details' })); - - await expect( - screen.findByText('Custom details'), - ).resolves.toBeInTheDocument(); - }); - - it('should capture click events in analytics', async () => { - // Mocking the analytics api implementation - const analyticsApiMock = new MockAnalyticsApi(); - - const analyticsApiOverride = createApiExtension({ - factory: createApiFactory({ - api: analyticsApiRef, - deps: {}, - factory: () => analyticsApiMock, - }), - }); - - const indexPageExtension = createExtension({ - ...defaultDefinition, - factory: () => { - const Component = () => { - const analyticsApi = useAnalytics(); - const handleClick = useCallback(() => { - analyticsApi.captureEvent('click', 'See details'); - }, [analyticsApi]); - return ( -
- Index Page - -
- ); - }; - - return { - element: , - }; - }, - }); - - const tester = createExtensionTester(indexPageExtension); - - // Overriding the analytics api extension - tester.add(analyticsApiOverride); - - tester.render(); - - fireEvent.click(await screen.findByRole('button', { name: 'See details' })); - - await waitFor(() => - expect(analyticsApiMock.getEvents()).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - action: 'click', - subject: 'See details', - }), - ]), - ), - ); - }); - it('should return the correct dataRef when called', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension); - expect(tester.data(stringDataRef)).toBe('test-text'); + expect(tester.get(stringDataRef)).toBe('test-text'); }); it('should throw an error if trying to access an instance not provided to the tester', () => { const extension = createExtension({ - namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const extension2 = createExtension({ - namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension); expect(() => tester.query(extension2)).toThrow( - "Extension with ID 'test/e2' not found, please make sure it's added to the tester", + "Extension with ID 'e2' not found, please make sure it's added to the tester", ); }); it('should throw an error if trying to access an instance which is not part of the tree', () => { const extension = createExtension({ - namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const extension2 = createExtension({ - namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + output: [stringDataRef], + factory: () => [stringDataRef('test-text')], }); const tester = createExtensionTester(extension).add(extension2); expect(() => tester.query(extension2)).toThrow( - "Extension with ID 'test/e2' has not been instantiated, because it is not part of the test subject's extension tree", + "Extension with ID 'e2' has not been instantiated, because it is not part of the test subject's extension tree", ); }); - // TODO: this should be implemented - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should allow querying an extension and getting outputs', () => { - const extension = createExtension({ - namespace: 'test', - name: 'e1', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - inputs: { - input: createExtensionInput( - { - output: stringDataRef, - }, - { singleton: true }, - ), - }, - factory: ({ inputs }) => ({ - text: `nest-${inputs.input.output.output}`, - }), + it('should not allow getting extension data for an output that was not defined in the extension', () => { + const internalRef = createExtensionDataRef().with({ + id: 'test.internal', }); - const extension2 = createExtension({ - namespace: 'test', + const internalRef2 = createExtensionDataRef().with({ + id: 'test.internal2', + }); + + const extension = createExtension({ + name: 'e1', + attachTo: { id: 'ignored', input: 'ignored' }, + output: [stringDataRef, internalRef.optional()], + factory: () => [stringDataRef('test-text')], + }); + + const tester = createExtensionTester(extension); + + const test: string = tester.get(stringDataRef); + + // @ts-expect-error - internalRef is optional + const test2: number = tester.get(internalRef); + + // @ts-expect-error - internalRef2 is not defined in the extension + const test3: number = tester.get(internalRef2); + + expect([test, test2, test3]).toBeDefined(); + }); + + it('should support getting outputs from a query response', () => { + const internalRef = createExtensionDataRef().with({ + id: 'test.internal', + }); + + const internalRef2 = createExtensionDataRef().with({ + id: 'test.internal2', + }); + + const extension = createExtension({ + name: 'e1', + inputs: { + ignored: createExtensionInput([stringDataRef]), + }, + attachTo: { id: 'ignored', input: 'ignored' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
bob
)], + }); + + const extraExtension = createExtension({ name: 'e2', - attachTo: { id: 'test/e1', input: 'blob' }, - output: { text: stringDataRef }, - factory: () => ({ text: 'test-text' }), + attachTo: { id: 'e1', input: 'ignored' }, + output: [stringDataRef, internalRef.optional()], + factory: () => [stringDataRef('test-text')], }); - const tester = createExtensionTester(extension).add(extension2); + const tester = createExtensionTester(extension) + .add(extraExtension) + .query(extraExtension); - expect(tester.query(extension).data(stringDataRef)).toBe('nest-test-text'); - expect(tester.query(extension2).data(stringDataRef)).toBe('test-text'); - // @ts-expect-error - expect(tester.query(extension).input('input').data(stringDataRef)).toBe( - 'nest-test-text', - ); - }); + const test: string = tester.get(stringDataRef); - // TODO: this should be implemented - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should allow defining inputs to extensions without a corresponding extension definition', () => { - const extension = createExtension({ - namespace: 'test', - name: 'e1', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { text: stringDataRef }, - inputs: { - input: createExtensionInput( - { - output: stringDataRef, - }, - { singleton: true }, - ), - }, - factory: ({ inputs }) => ({ - text: `nest-${inputs.input.output.output}`, - }), - }); + // @ts-expect-error - internalRef is optional + const test2: number = tester.get(internalRef); - const tester = createExtensionTester(extension, { - // @ts-expect-error - inputs: { input: 'test-text' }, - }); + // @ts-expect-error - internalRef2 is not defined in the extension + const test3: number = tester.get(internalRef2); - expect(tester.query(extension).data(stringDataRef)).toBe('nest-test-text'); + expect([test, test2, test3]).toBeDefined(); }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 4bfc8392cb..f9c1b3cb89 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -14,25 +14,15 @@ * limitations under the License. */ -import React from 'react'; -import { MemoryRouter, Link } from 'react-router-dom'; -import { RenderResult, render } from '@testing-library/react'; -import { createSpecializedApp } from '@backstage/frontend-app-api'; import { + AnyExtensionDataRef, AppNode, AppTree, Extension, ExtensionDataRef, ExtensionDefinition, - IconComponent, - RouteRef, + ExtensionDefinitionParameters, coreExtensionData, - createExtension, - createExtensionInput, - createExtensionOverrides, - createNavItemExtension, - createRouterExtension, - useRouteRef, } from '@backstage/frontend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; @@ -48,60 +38,10 @@ import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveA import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/instantiateAppNodeTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; - -const NavItem = (props: { - routeRef: RouteRef; - title: string; - icon: IconComponent; -}) => { - const { routeRef, title, icon: Icon } = props; - const link = useRouteRef(routeRef); - if (!link) { - return null; - } - return ( -
  • - - {title} - -
  • - ); -}; - -const TestAppNavExtension = createExtension({ - namespace: 'app', - name: 'nav', - attachTo: { id: 'app/layout', input: 'nav' }, - inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, - }), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - ), - }; - }, -}); +import { TestApiRegistry } from '@backstage/test-utils'; /** @public */ -export class ExtensionQuery { +export class ExtensionQuery { #node: AppNode; constructor(node: AppNode) { @@ -124,59 +64,26 @@ export class ExtensionQuery { return instance; } - data(ref: ExtensionDataRef): T | undefined { + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never { return this.instance.getData(ref); } } /** @public */ -export class ExtensionTester { +export class ExtensionTester { /** @internal */ - static forSubject( - subject: ExtensionDefinition, - options?: { config?: TConfigInput }, - ): ExtensionTester { + static forSubject( + subject: ExtensionDefinition, + options?: { config?: T['configInput'] }, + ): ExtensionTester> { const tester = new ExtensionTester(); - const internal = toInternalExtensionDefinition(subject); - - // attaching to app/routes to render as index route - if (internal.version === 'v1') { - tester.add( - createExtension({ - ...internal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: { - ...internal.output, - path: coreExtensionData.routePath, - }, - factory: params => ({ - ...internal.factory(params as any), - path: '/', - }), - }), - options as TConfigInput & {}, - ); - } else if (internal.version === 'v2') { - tester.add( - createExtension({ - ...internal, - attachTo: { id: 'app/routes', input: 'routes' }, - output: internal.output.find( - ref => ref.id === coreExtensionData.routePath.id, - ) - ? internal.output - : [...internal.output, coreExtensionData.routePath], - factory: params => { - const parentOutput = Array.from( - internal.factory(params as any), - ).filter(val => val.id !== coreExtensionData.routePath.id); - - return [...parentOutput, coreExtensionData.routePath('/')]; - }, - }), - options as TConfigInput & {}, - ); - } + tester.add(subject, options as T['configInput'] & {}); return tester; } @@ -185,21 +92,21 @@ export class ExtensionTester { readonly #extensions = new Array<{ id: string; extension: Extension; - definition: ExtensionDefinition; + definition: ExtensionDefinition; config?: JsonValue; }>(); - add( - extension: ExtensionDefinition, - options?: { config?: TConfigInput }, - ): ExtensionTester { + add( + extension: ExtensionDefinition, + options?: { config?: T['configInput'] }, + ): ExtensionTester { if (this.#tree) { throw new Error( 'Cannot add more extensions accessing the extension tree', ); } - const { name, namespace } = extension; + const { name, namespace } = toInternalExtensionDefinition(extension); const definition = { ...extension, @@ -219,17 +126,30 @@ export class ExtensionTester { return this; } - data(ref: ExtensionDataRef): T | undefined { + get( + ref: ExtensionDataRef, + ): UOutput extends ExtensionDataRef + ? IConfig['optional'] extends true + ? IData | undefined + : IData + : never { const tree = this.#resolveTree(); - return new ExtensionQuery(tree.root).data(ref); + return new ExtensionQuery(tree.root).get(ref); } - query(id: string | ExtensionDefinition): ExtensionQuery { + query( + extension: ExtensionDefinition, + ): ExtensionQuery> { const tree = this.#resolveTree(); - const actualId = - typeof id === 'string' ? id : resolveExtensionDefinition(id).id; + // Same fallback logic as in .add + const { name, namespace } = toInternalExtensionDefinition(extension); + const definition = { + ...extension, + name: !namespace && !name ? 'test' : name, + }; + const actualId = resolveExtensionDefinition(definition).id; const node = tree.nodes.get(actualId); @@ -245,35 +165,20 @@ export class ExtensionTester { return new ExtensionQuery(node); } - render(options?: { config?: JsonObject }): RenderResult { - const { config = {} } = options ?? {}; + reactElement(): JSX.Element { + const tree = this.#resolveTree(); - const [subject] = this.#extensions; - if (!subject) { + const element = new ExtensionQuery(tree.root).get( + coreExtensionData.reactElement, + ); + + if (!element) { throw new Error( - 'No subject found. At least one extension should be added to the tester.', + 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(...)` to access output data directly instead', ); } - const app = createSpecializedApp({ - features: [ - createExtensionOverrides({ - extensions: [ - ...this.#extensions.map(extension => extension.definition), - TestAppNavExtension, - createRouterExtension({ - namespace: 'test', - Component: ({ children }) => ( - {children} - ), - }), - ], - }), - ], - config: this.#getConfig(config), - }); - - return render(app.createRoot()); + return element; } #resolveTree() { @@ -297,7 +202,7 @@ export class ExtensionTester { }), ); - instantiateAppNodeTree(tree.root); + instantiateAppNodeTree(tree.root, TestApiRegistry.from()); this.#tree = tree; @@ -336,9 +241,9 @@ export class ExtensionTester { } /** @public */ -export function createExtensionTester( - subject: ExtensionDefinition, - options?: { config?: TConfig }, -): ExtensionTester { +export function createExtensionTester( + subject: ExtensionDefinition, + options?: { config?: T['configInput'] }, +): ExtensionTester> { return ExtensionTester.forSubject(subject, options); } diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index e0483f68d8..e53566a118 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -15,12 +15,26 @@ */ import React from 'react'; +import { Link, MemoryRouter } from 'react-router-dom'; +import { + createSpecializedApp, + FrontendFeature, +} from '@backstage/frontend-app-api'; +import { RenderResult, render } from '@testing-library/react'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; import { - RouteRef, - coreExtensionData, createExtension, + ExtensionDefinition, + coreExtensionData, + RouteRef, + useRouteRef, + IconComponent, + RouterBlueprint, + NavItemBlueprint, + createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -import { createExtensionTester } from './createExtensionTester'; +import appPlugin from '@backstage/plugin-app'; /** * Options to customize the behavior of the test app. @@ -44,8 +58,74 @@ export type TestAppOptions = { * ``` */ mountedRoutes?: { [path: string]: RouteRef }; + + /** + * Additional configuration passed to the app when rendering elements inside it. + */ + config?: JsonObject; + + /** + * Additional extensions to add to the test app. + */ + extensions?: ExtensionDefinition[]; + + /** + * Additional features to add to the test app. + */ + features?: FrontendFeature[]; }; +const NavItem = (props: { + routeRef: RouteRef; + title: string; + icon: IconComponent; +}) => { + const { routeRef, title, icon: Icon } = props; + const link = useRouteRef(routeRef); + if (!link) { + return null; + } + return ( +
  • + + {title} + +
  • + ); +}; + +const appPluginOverride = appPlugin.withOverrides({ + extensions: [ + appPlugin.getExtension('app/nav').override({ + output: [coreExtensionData.reactElement], + factory(_originalFactory, { inputs }) { + return [ + coreExtensionData.reactElement( + , + ), + ]; + }, + }), + ], +}); + /** * @public * Renders the given element in a test app, for use in unit tests. @@ -53,36 +133,70 @@ export type TestAppOptions = { export function renderInTestApp( element: JSX.Element, options?: TestAppOptions, -) { - const extension = createExtension({ - namespace: 'test', - attachTo: { id: 'app', input: 'root' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ element }), - }); - const tester = createExtensionTester(extension); +): RenderResult { + const extensions: Array = [ + createExtension({ + attachTo: { id: 'app/routes', input: 'routes' }, + output: [coreExtensionData.reactElement, coreExtensionData.routePath], + factory: () => { + return [ + coreExtensionData.reactElement(element), + coreExtensionData.routePath('/'), + ]; + }, + }), + RouterBlueprint.make({ + params: { + Component: ({ children }) => {children}, + }, + }), + ]; if (options?.mountedRoutes) { for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { // TODO(Rugvip): add support for external route refs - tester.add( + extensions.push( createExtension({ kind: 'test-route', name: path, attachTo: { id: 'app/root', input: 'elements' }, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef, - }, - factory() { - return { element: , path, routeRef }; - }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef, + ], + factory: () => [ + coreExtensionData.reactElement(), + coreExtensionData.routePath(path), + coreExtensionData.routeRef(routeRef), + ], }), ); } } - return tester.render(); + + if (options?.extensions) { + extensions.push(...options.extensions); + } + + const features: FrontendFeature[] = [ + createFrontendPlugin({ + id: 'test', + extensions, + }), + appPluginOverride, + ]; + + if (options?.features) { + features.push(...options.features); + } + + const app = createSpecializedApp({ + features, + config: ConfigReader.fromConfigs([ + { context: 'render-config', data: options?.config ?? {} }, + ]), + }); + + return render(app.createRoot()); } diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 36ecf6f718..a5bf99ba4d 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.1.30-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index de42eb2798..0ae77a9bd2 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.1.30-next.0", + "version": "1.1.30", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index b49c3410ec..9e8fef7af8 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 + ## 1.14.0-next.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 0d1b733bbf..83c6e4ce8b 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.14.0-next.0", + "version": "1.14.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index f74dd57ea0..7ffae42333 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/repo-tools +## 0.9.7-next.1 + +### Patch Changes + +- 5c4aa2f: Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + +## 0.9.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.9.5-next.2 ### Patch Changes diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index a06f426baf..6e0e4c9b85 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -18,6 +18,7 @@ Commands: knip-reports [options] [paths...] package [command] repo [command] + lint [command] help [command] ``` @@ -60,6 +61,28 @@ Options: -h, --help ``` +### `backstage-repo-tools lint` + +``` +Usage: backstage-repo-tools lint [options] [command] [command] + +Options: + -h, --help + +Commands: + legacy-backend-exports [paths...] + help [command] +``` + +### `backstage-repo-tools lint legacy-backend-exports` + +``` +Usage: backstage-repo-tools lint legacy-backend-exports [options] [paths...] + +Options: + -h, --help +``` + ### `backstage-repo-tools package` ``` diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 63be4d526e..89b2f0f124 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.5-next.2", + "version": "0.9.7-next.1", "publishConfig": { "access": "public" }, @@ -60,7 +60,7 @@ "@stoplight/spectral-rulesets": "^1.18.0", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "^14.0.0", - "@useoptic/openapi-utilities": "^0.54.8", + "@useoptic/openapi-utilities": "^0.55.0", "chalk": "^4.0.0", "codeowners-utils": "^1.0.2", "command-exists": "^1.2.9", @@ -73,6 +73,7 @@ "minimatch": "^9.0.0", "p-limit": "^3.0.2", "portfinder": "^1.0.32", + "ts-morph": "^23.0.0", "yaml-diff-patch": "^2.0.0" }, "devDependencies": { diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 24ec2af411..5116326eea 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -162,6 +162,23 @@ function registerRepoCommand(program: Command) { ); } +function registerLintCommand(program: Command) { + const lintCommand = program + .command('lint [command]') + .description('Tools for linting repository.'); + lintCommand + .command('legacy-backend-exports [paths...]') + .description( + 'Lint backend plugin packages for legacy exports and make sure it conforms to the new export pattern', + ) + .action( + lazy(() => + import( + './lint-legacy-backend-exports/lint-legacy-backend-exports' + ).then(m => m.lint), + ), + ); +} export function registerCommands(program: Command) { program .command('api-reports [paths...]') @@ -238,6 +255,7 @@ export function registerCommands(program: Command) { registerPackageCommand(program); registerRepoCommand(program); + registerLintCommand(program); } // Wraps an action function so that it always exits and handles errors diff --git a/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts b/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts new file mode 100644 index 0000000000..6025defe31 --- /dev/null +++ b/packages/repo-tools/src/commands/lint-legacy-backend-exports/lint-legacy-backend-exports.ts @@ -0,0 +1,98 @@ +/* + * 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 { Project } from 'ts-morph'; +import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; +import fs from 'fs-extra'; +import { paths as cliPaths } from '../../lib/paths'; +import path from 'path'; + +const project = new Project({ + tsConfigFilePath: cliPaths.resolveTargetRoot('tsconfig.json'), +}); + +function readPackageJson(pkg: string) { + return JSON.parse(fs.readFileSync(path.join(pkg, 'package.json'), 'utf-8')); +} + +export async function lint(paths: string[]) { + const pkgs = (await PackageGraph.listTargetPackages()).filter(pkg => { + return ( + pkg.packageJson.backstage?.role === 'backend-plugin' || + pkg.packageJson.backstage?.role === 'backend-plugin-module' + ); + }); + + if (paths.length > 0) { + paths.forEach(pkg => verifyIndex(pkg)); + return; + } + pkgs.forEach(pkg => verifyIndex(pkg.dir, pkg.packageJson)); +} + +function verifyIndex(pkg: string, packageJson?: BackstagePackageJson) { + console.log(`Verifying ${pkg}`); + const tsPath = path.join(pkg, 'src/index.ts'); + const sourceFile = project.getSourceFile(tsPath); + if (!sourceFile) { + console.log(`Could not find ${tsPath}`); + process.exit(1); + } + const symbols = sourceFile?.getExportSymbols(); + + const exportCount = symbols?.length || 0; + if (exportCount > 1) { + console.log( + ` ⚠️ Warning: ${exportCount} exports found, ${symbols + .map(symbol => symbol.getName()) + .join(', ')}`, + ); + } + + const createRouterExport = symbols?.find( + symbol => symbol.getName() === 'createRouter', + ); + + if (!sourceFile.getDefaultExportSymbol()) { + console.log(' ❌ Missing default export'); + } + let createRouterDeprecated = undefined; + if (createRouterExport) { + createRouterDeprecated = createRouterExport + .getJsDocTags() + .find(tag => tag.getName() === 'deprecated'); + } + + if (createRouterExport) { + console.log(' ❌ createRouter is exported'); + if (!createRouterDeprecated) + console.log(' ❌ createRouter is NOT deprecated'); + } + + const pkgJson = packageJson ?? readPackageJson(pkg); + if ( + '@backstage/backend-common' in pkgJson.dependencies || + '@backstage/backend-common' in pkgJson.devDependencies + ) { + console.log(' ❌ Stop depending on @backstage/backend-common'); + } + + if ( + '@backstage/backend-tasks' in pkgJson.dependencies || + '@backstage/backend-tasks' in pkgJson.devDependencies + ) { + console.log(' ❌ Stop depending on @backstage/backend-tasks'); + } +} diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 57fb204390..77a6e403bd 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,81 @@ # techdocs-cli-embedded-app +## 0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## 0.2.100-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/plugin-techdocs@1.10.9-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/cli@0.27.1-next.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/app-defaults@1.5.10 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## 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 + +## 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 + ## 0.2.99-next.3 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 472936193a..83bc610762 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.99-next.3", + "version": "0.2.100-next.1", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 76f9891bf0..a7c62bd94b 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,49 @@ # @techdocs/cli +## 1.8.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## 1.8.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.11-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 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 + +## 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 + ## 1.8.17-next.2 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index c655649ceb..64a6ebc772 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.17-next.2", + "version": "1.8.19-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index a6c013a34a..29d38987d7 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/test-utils +## 1.6.0-next.0 + +### Minor Changes + +- d47be30: Added the icons option to the renderInTestApp function's TestAppOptions to be forwarded to the icons option of `createApp`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## 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 + ## 1.5.10-next.2 ### Patch Changes diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index cc1c645387..ae54c588ba 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -8,6 +8,7 @@ import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; import { AppComponents } from '@backstage/core-plugin-api'; +import { AppIcons } from '@backstage/core-app-api'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ComponentType } from 'react'; import { Config } from '@backstage/config'; @@ -21,6 +22,7 @@ import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; @@ -252,6 +254,9 @@ export type TestAppOptions = { [path: string]: RouteRef | ExternalRouteRef; }; components?: Partial; + icons?: Partial & { + [key in string]: IconComponent; + }; }; // @public diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 3007c734bd..16d1609c66 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.5.10-next.2", + "version": "1.6.0-next.0", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index f2c1ff592e..3a55389b74 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -23,13 +23,14 @@ import React, { import { MemoryRouter, Route } from 'react-router-dom'; import { themes, UnifiedThemeProvider } from '@backstage/theme'; import MockIcon from '@material-ui/icons/AcUnit'; -import { createSpecializedApp } from '@backstage/core-app-api'; +import { AppIcons, createSpecializedApp } from '@backstage/core-app-api'; import { AppComponents, attachComponentData, BootErrorPageProps, createRouteRef, ExternalRouteRef, + IconComponent, RouteRef, } from '@backstage/core-plugin-api'; import { MatcherFunction, RenderResult } from '@testing-library/react'; @@ -107,6 +108,13 @@ export type TestAppOptions = { * Components to be forwarded to the `components` option of `createApp`. */ components?: Partial; + + /** + * Icons to be forwarded to the `icons` option of `createApp`. + */ + icons?: Partial & { + [key in string]: IconComponent; + }; }; function isExternalRouteRef( @@ -145,7 +153,10 @@ export function createTestAppWrapper( ), ...options.components, }, - icons: mockIcons, + icons: { + ...mockIcons, + ...options.icons, + }, plugins: [], themes: [ { diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index c87bfcaafa..f75835f0a6 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/plugin-api-docs +## 0.11.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## 0.11.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-catalog@1.22.1-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## 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 + +## 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 + ## 0.11.8-next.2 ### Patch Changes diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index 676a2c66c1..eb84ef8c27 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -50,6 +50,7 @@ To link that a component provides or consumes an API, see the [`providesApis`](h - [Custom Api Renderings](#custom-api-renderings) - [Adding Swagger UI Interceptor](#adding-requestinterceptor-to-swagger-ui) - [Providing Swagger UI Specific Supported Methods](#provide-specific-supported-methods-to-swagger-ui) + - [Custom Resolvers for AsyncApi](#custom-resolvers-for-asyncapi) - [Integrations](#integrations) - [Implementing OAuth 2 Authorization Code flow with Swagger UI](#implementing-oauth-2-authorization-code-flow-with-swagger-ui) @@ -1092,6 +1093,61 @@ export default createExtensionOverrides({ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. +##### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + ### Integrations #### Implementing OAuth 2 Authorization Code flow with Swagger UI diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 2387752cc9..2738cc09ae 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -222,10 +222,6 @@ security: - [read_pets, write_pets] ``` -## Links - -- [The Backstage homepage](https://backstage.io) - ### Adding `requestInterceptor` to Swagger UI To configure a [`requestInterceptor` for Swagger UI](https://github.com/swagger-api/swagger-ui/tree/master/flavors/swagger-ui-react#requestinterceptor-proptypesfunc) you'll need to add the following to your `api.tsx`: @@ -319,3 +315,62 @@ export const apis: AnyApiFactory[] = [ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. + +### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + +## Links + +- [The Backstage homepage](https://backstage.io) diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index 6003056cb3..bb2da7bc0b 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -3,19 +3,387 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } 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 { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } 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'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, { registerApi: ExternalRouteRef; }, - {} + { + 'nav-item:api-docs': ExtensionDefinition<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + }>; + 'api:api-docs/config': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'config'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'page:api-docs': ExtensionDefinition<{ + config: { + initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined; + } & { + path: string | undefined; + }; + configInput: { + initiallySelectedFilter?: 'all' | 'owned' | 'starred' | undefined; + } & { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'page'; + namespace: undefined; + name: undefined; + }>; + 'entity-card:api-docs/has-apis': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-apis'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:api-docs/definition': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'definition'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:api-docs/consumed-apis': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'consumed-apis'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:api-docs/provided-apis': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'provided-apis'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:api-docs/consuming-components': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'consuming-components'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:api-docs/providing-components': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'providing-components'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-content:api-docs/definition': ExtensionDefinition<{ + kind: 'entity-content'; + namespace: undefined; + name: 'definition'; + config: { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | 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; + } + >; + inputs: {}; + }>; + 'entity-content:api-docs/apis': ExtensionDefinition<{ + kind: 'entity-content'; + namespace: undefined; + name: 'apis'; + config: { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | 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; + } + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 2fc852777b..3c3f245a3c 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -9,6 +9,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; +import { ComponentEntity } from '@backstage/catalog-model'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; @@ -16,6 +17,7 @@ import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TableColumn } from '@backstage/core-components'; +import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; @@ -83,17 +85,29 @@ export const AsyncApiDefinitionWidget: ( // @public (undocumented) export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; +}; + +// @public (undocumented) +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; }; // @public (undocumented) export const ConsumedApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) export const ConsumingComponentsCard: (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => React_2.JSX.Element; // @public @@ -118,29 +132,37 @@ export const EntityApiDefinitionCard: () => JSX_2.Element; // @public (undocumented) export const EntityConsumedApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) export const EntityConsumingComponentsCard: (props: { variant?: InfoCardVariants | undefined; + columns?: TableColumn[] | undefined; }) => JSX_2.Element; // @public (undocumented) export const EntityHasApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) export const EntityProvidedApisCard: (props: { variant?: InfoCardVariants | undefined; + title?: string | undefined; columns?: TableColumn[] | undefined; + tableOptions?: TableOptions<{}> | undefined; }) => JSX_2.Element; // @public (undocumented) export const EntityProvidingComponentsCard: (props: { variant?: InfoCardVariants | undefined; + columns?: TableColumn[] | undefined; }) => JSX_2.Element; // @public (undocumented) @@ -156,7 +178,9 @@ export type GraphQlDefinitionWidgetProps = { // @public (undocumented) export const HasApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) @@ -185,12 +209,15 @@ export type PlainApiDefinitionWidgetProps = { // @public (undocumented) export const ProvidedApisCard: (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => React_2.JSX.Element; // @public (undocumented) export const ProvidingComponentsCard: (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => React_2.JSX.Element; // @public (undocumented) diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 96b52002d8..3a102a70e5 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.8-next.2", + "version": "0.11.9-next.1", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 48dae28cdd..b38bfc6c91 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -18,12 +18,11 @@ import React from 'react'; import Grid from '@material-ui/core/Grid'; import { - createApiExtension, + ApiBlueprint, + NavItemBlueprint, + PageBlueprint, createApiFactory, - createNavItemExtension, - createPageExtension, - createPlugin, - createSchemaFromZod, + createFrontendPlugin, } from '@backstage/frontend-plugin-api'; import { @@ -31,10 +30,6 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { - createEntityCardExtension, - createEntityContentExtension, -} from '@backstage/plugin-catalog-react/alpha'; import { ApiEntity, parseEntityRef, @@ -46,164 +41,193 @@ import { rootRoute, registerComponentRouteRef } from './routes'; import { apiDocsConfigRef } from './config'; import { AppIcon } from '@backstage/core-components'; -const apiDocsNavItem = createNavItemExtension({ - title: 'APIs', - routeRef: convertLegacyRouteRef(rootRoute), - icon: () => compatWrapper(), -}); +import { + EntityCardBlueprint, + EntityContentBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; -const apiDocsConfigApi = createApiExtension({ - factory: createApiFactory({ - api: apiDocsConfigRef, - deps: {}, - factory: () => { - const definitionWidgets = defaultDefinitionWidgets(); - return { - getApiDefinitionWidget: (apiEntity: ApiEntity) => { - return definitionWidgets.find(d => d.type === apiEntity.spec.type); - }, - }; - }, - }), -}); - -const apiDocsExplorerPage = createPageExtension({ - defaultPath: '/api-docs', - routeRef: convertLegacyRouteRef(rootRoute), - // Mapping DefaultApiExplorerPageProps to config - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default('/api-docs'), - initiallySelectedFilter: z.enum(['owned', 'starred', 'all']).optional(), - // Ommiting columns and actions for now as their types are too complex to map to zod - }), - ), - loader: ({ config }) => - import('./components/ApiExplorerPage').then(m => - compatWrapper( - , - ), - ), -}); - -const apiDocsHasApisEntityCard = createEntityCardExtension({ - name: 'has-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: entity => { - return ( - entity.kind === 'Component' && - entity.relations?.some( - ({ type, targetRef }) => - type.toLocaleLowerCase('en-US') === RELATION_HAS_PART && - parseEntityRef(targetRef).kind === 'API', - )!! - ); +const apiDocsNavItem = NavItemBlueprint.make({ + params: { + title: 'APIs', + routeRef: convertLegacyRouteRef(rootRoute), + icon: () => compatWrapper(), }, - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), }); -const apiDocsDefinitionEntityCard = createEntityCardExtension({ +const apiDocsConfigApi = ApiBlueprint.make({ + name: 'config', + params: { + factory: createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const definitionWidgets = defaultDefinitionWidgets(); + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + }, + }), + }, +}); + +const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ + config: { + schema: { + // Ommiting columns and actions for now as their types are too complex to map to zod + initiallySelectedFilter: z => + z.enum(['owned', 'starred', 'all']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + defaultPath: '/api-docs', + routeRef: convertLegacyRouteRef(rootRoute), + loader: () => + import('./components/ApiExplorerPage').then(m => + compatWrapper( + , + ), + ), + }); + }, +}); + +const apiDocsHasApisEntityCard = EntityCardBlueprint.make({ + name: 'has-apis', + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: entity => { + return ( + entity.kind === 'Component' && + entity.relations?.some( + ({ type, targetRef }) => + type.toLocaleLowerCase('en-US') === RELATION_HAS_PART && + parseEntityRef(targetRef).kind === 'API', + )!! + ); + }, + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, +}); + +const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({ name: 'definition', - filter: 'kind:api', - loader: () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:api', + loader: () => + import('./components/ApiDefinitionCard').then(m => + compatWrapper(), + ), + }, }); -const apiDocsConsumedApisEntityCard = createEntityCardExtension({ +const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ name: 'consumed-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:component', + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsProvidedApisEntityCard = createEntityCardExtension({ +const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ name: 'provided-apis', - // Ommiting configSchema for now - // We are skipping variants and columns are too complex to map to zod - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:component', - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants and columns are too complex to map to zod + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:component', + loader: () => + import('./components/ApisCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsConsumingComponentsEntityCard = createEntityCardExtension({ +const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ name: 'consuming-components', - // Ommiting configSchema for now - // We are skipping variants - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', - loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:api', + loader: () => + import('./components/ComponentsCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsProvidingComponentsEntityCard = createEntityCardExtension({ +const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ name: 'providing-components', - // Ommiting configSchema for now - // We are skipping variants - // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - filter: 'kind:api', - loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + params: { + // Ommiting configSchema for now + // We are skipping variants + // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + filter: 'kind:api', + loader: () => + import('./components/ComponentsCards').then(m => + compatWrapper(), + ), + }, }); -const apiDocsDefinitionEntityContent = createEntityContentExtension({ +const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ name: 'definition', - defaultPath: '/defintion', - defaultTitle: 'Definition', - filter: 'kind:api', - loader: async () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper( - - - - - , + params: { + defaultPath: '/defintion', + defaultTitle: 'Definition', + filter: 'kind:api', + loader: async () => + import('./components/ApiDefinitionCard').then(m => + compatWrapper( + + + + + , + ), ), - ), + }, }); -const apiDocsApisEntityContent = createEntityContentExtension({ +const apiDocsApisEntityContent = EntityContentBlueprint.make({ name: 'apis', - defaultPath: '/apis', - defaultTitle: 'APIs', - filter: 'kind:component', - loader: async () => - import('./components/ApisCards').then(m => - compatWrapper( - - - - - - - - , + params: { + defaultPath: '/apis', + defaultTitle: 'APIs', + filter: 'kind:component', + loader: async () => + import('./components/ApisCards').then(m => + compatWrapper( + + + + + + + + , + ), ), - ), + }, }); -export default createPlugin({ +export default createFrontendPlugin({ id: 'api-docs', routes: { root: convertLegacyRouteRef(rootRoute), diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index d5494d1818..f68e6dcb40 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -38,9 +39,16 @@ import { */ export const ConsumedApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = apiEntityColumns } = props; + const { + variant = 'gridItem', + title = 'Consumed APIs', + columns = apiEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_CONSUMES_API, @@ -48,7 +56,7 @@ export const ConsumedApisCard = (props: { if (loading) { return ( - + ); @@ -56,7 +64,7 @@ export const ConsumedApisCard = (props: { if (error || !entities) { return ( - + @@ -84,6 +92,7 @@ export const ConsumedApisCard = (props: {
    } columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index d58a654176..6810d9d2ba 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -46,9 +47,16 @@ const presetColumns: TableColumn[] = [ */ export const HasApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = presetColumns } = props; + const { + variant = 'gridItem', + title = 'APIs', + columns = presetColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_HAS_PART, @@ -57,7 +65,7 @@ export const HasApisCard = (props: { if (loading) { return ( - + ); @@ -65,7 +73,7 @@ export const HasApisCard = (props: { if (error || !entities) { return ( - + @@ -93,6 +101,7 @@ export const HasApisCard = (props: {
    } columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 97d0a00b0a..48099df375 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -30,6 +30,7 @@ import { Link, Progress, TableColumn, + TableOptions, WarningPanel, } from '@backstage/core-components'; @@ -38,9 +39,16 @@ import { */ export const ProvidedApisCard = (props: { variant?: InfoCardVariants; + title?: string; columns?: TableColumn[]; + tableOptions?: TableOptions; }) => { - const { variant = 'gridItem', columns = apiEntityColumns } = props; + const { + variant = 'gridItem', + title = 'Provided APIs', + columns = apiEntityColumns, + tableOptions = {}, + } = props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_PROVIDES_API, @@ -48,7 +56,7 @@ export const ProvidedApisCard = (props: { if (loading) { return ( - + ); @@ -56,7 +64,7 @@ export const ProvidedApisCard = (props: { if (error || !entities) { return ( - + @@ -84,6 +92,7 @@ export const ProvidedApisCard = (props: {
    } columns={columns} + tableOptions={tableOptions} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 85ce15132a..4817774182 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -20,6 +20,14 @@ import { makeStyles, alpha, darken } from '@material-ui/core/styles'; import React from 'react'; import { useTheme } from '@material-ui/core/styles'; +/** @public */ +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; +}; + const useStyles = makeStyles(theme => ({ root: { fontFamily: 'inherit', @@ -143,7 +151,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const httpsFetchResolver = { +const httpsFetchResolver: AsyncApiResolver = { schema: 'https', order: 1, canRead: true, @@ -153,7 +161,7 @@ const httpsFetchResolver = { }, }; -const httpFetchResolver = { +const httpFetchResolver: AsyncApiResolver = { schema: 'http', order: 1, canRead: true, @@ -163,27 +171,36 @@ const httpFetchResolver = { }, }; -const config = { - parserOptions: { - __unstable: { - resolver: { - resolvers: [httpsFetchResolver, httpFetchResolver], - }, - }, - }, -}; - type Props = { definition: string; + resolvers?: AsyncApiResolver[]; }; -export const AsyncApiDefinition = ({ definition }: Props): JSX.Element => { +export const AsyncApiDefinition = ({ + definition, + resolvers, +}: Props): JSX.Element => { const classes = useStyles(); const theme = useTheme(); const classNames = `${classes.root} ${ theme.palette.type === 'dark' ? classes.dark : '' }`; + const config = { + parserOptions: { + __unstable: { + resolver: { + resolvers: [httpsFetchResolver, httpFetchResolver], + }, + }, + }, + }; + + // Overwrite default resolvers if custom ones are set + if (resolvers) { + config.parserOptions.__unstable.resolver.resolvers = resolvers; + } + return (
    diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index 2e8710b008..104a55e576 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -16,6 +16,7 @@ import { Progress } from '@backstage/core-components'; import React, { Suspense } from 'react'; +import { AsyncApiResolver } from './AsyncApiDefinition'; // The asyncapi component and related CSS has a significant size, only load it // if the element is actually used. @@ -28,6 +29,7 @@ const LazyAsyncApiDefinition = React.lazy(() => /** @public */ export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; }; /** @public */ diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index 7cc49e3138..3b07c32cfe 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -16,3 +16,4 @@ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; +export type { AsyncApiResolver } from './AsyncApiDefinition'; diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 5feb1c96e4..8e461bb08a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -31,6 +31,7 @@ import { InfoCardVariants, Link, Progress, + TableColumn, WarningPanel, } from '@backstage/core-components'; @@ -39,8 +40,10 @@ import { */ export const ConsumingComponentsCard = (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => { - const { variant = 'gridItem' } = props; + const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = + props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_CONSUMED_BY, @@ -82,7 +85,7 @@ export const ConsumingComponentsCard = (props: {
    } - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index ef7f2e1071..33dc7217d4 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -31,14 +31,17 @@ import { InfoCardVariants, Link, Progress, + TableColumn, WarningPanel, } from '@backstage/core-components'; /** @public */ export const ProvidingComponentsCard = (props: { variant?: InfoCardVariants; + columns?: TableColumn[]; }) => { - const { variant = 'gridItem' } = props; + const { variant = 'gridItem', columns = EntityTable.componentEntityColumns } = + props; const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: RELATION_API_PROVIDED_BY, @@ -80,7 +83,7 @@ export const ProvidingComponentsCard = (props: {
    } - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 305d90e2ec..a350fb994e 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-app-backend +## 0.3.74-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + +## 0.3.74-next.0 + +### Patch Changes + +- d3f79d1: Fixing dependency metadata with the new `@backstage/plugin-app` package +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-app-node@0.1.25-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 + +## 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 + ## 0.3.72-next.2 ### Patch Changes diff --git a/plugins/app-backend/api-report-alpha.md b/plugins/app-backend/api-report-alpha.md index b51096b8f4..1c9b6b2902 100644 --- a/plugins/app-backend/api-report-alpha.md +++ b/plugins/app-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 appPlugin: BackendFeatureCompat; +const appPlugin: BackendFeature; export default appPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/app-backend/api-report.md b/plugins/app-backend/api-report.md index 9ef0f97dab..bb0ebb67db 100644 --- a/plugins/app-backend/api-report.md +++ b/plugins/app-backend/api-report.md @@ -4,24 +4,24 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import express from 'express'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginDatabaseManager } from '@backstage/backend-common'; +import { RootConfigService } from '@backstage/backend-plugin-api'; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { appPackageName: string; // (undocumented) auth?: AuthService; // (undocumented) - config: Config; - database?: PluginDatabaseManager; + config: RootConfigService; + database?: DatabaseService; disableConfigInjection?: boolean; // (undocumented) httpAuth?: HttpAuthService; diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d6ac29953e..4f438a7d9b 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.72-next.2", + "version": "0.3.74-next.1", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", "pluginId": "app", "pluginPackages": [ + "@backstage/plugin-app", "@backstage/plugin-app-backend", "@backstage/plugin-app-node" ] diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 8ec871df81..b205f16203 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -14,12 +14,13 @@ * limitations under the License. */ +import { notFoundHandler } from '@backstage/backend-common'; import { - notFoundHandler, - PluginDatabaseManager, -} from '@backstage/backend-common'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; -import { AppConfig, Config } from '@backstage/config'; + DatabaseService, + resolvePackagePath, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { AppConfig } from '@backstage/config'; import helmet from 'helmet'; import express from 'express'; import Router from 'express-promise-router'; @@ -47,9 +48,12 @@ import { AuthenticationError } from '@backstage/errors'; // express uses mime v1 while we only have types for mime v2 type Mime = { lookup(arg0: string): string }; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { - config: Config; + config: RootConfigService; logger: LoggerService; auth?: AuthService; httpAuth?: HttpAuthService; @@ -59,7 +63,7 @@ export interface RouterOptions { * * This is a built-in alternative to using a `staticFallbackHandler`. */ - database?: PluginDatabaseManager; + database?: DatabaseService; /** * The name of the app package that content should be served from. The same app package should be @@ -102,7 +106,10 @@ export interface RouterOptions { schema?: ConfigSchema; } -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export async function createRouter( options: RouterOptions, ): Promise { diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 25861bc8a1..feae31a8ea 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-app-node +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config-loader@1.9.0 + +## 0.1.25-next.0 + +### Patch Changes + +- d3f79d1: Fixing dependency metadata with the new `@backstage/plugin-app` package +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/config-loader@1.9.0 + +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + +## 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 + ## 0.1.23-next.2 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index e5e4d19a47..33af5bfba1 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.23-next.2", + "version": "0.1.25-next.1", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", "pluginId": "app", "pluginPackages": [ + "@backstage/plugin-app", "@backstage/plugin-app-backend", "@backstage/plugin-app-node" ] diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index a7f8d8a012..2afc4a1651 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-app-visualizer +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## 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 + +## 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 + ## 0.1.9-next.2 ### Patch Changes diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md index fd3749b159..e542674845 100644 --- a/plugins/app-visualizer/api-report.md +++ b/plugins/app-visualizer/api-report.md @@ -3,10 +3,64 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } 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'; // @public (undocumented) -const visualizerPlugin: BackstagePlugin<{}, {}, {}>; +const visualizerPlugin: FrontendPlugin< + {}, + {}, + { + 'page:app-visualizer': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + 'nav-item:app-visualizer': ExtensionDefinition<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + }>; + } +>; export default visualizerPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 0d825be137..3e24482eda 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.9-next.2", + "version": "0.1.10-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", @@ -38,11 +38,11 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1" + "@material-ui/icons": "^4.9.1", + "@types/react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "@types/react": "^16.13.1 || ^17.0.0" + "@backstage/cli": "workspace:^" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index b2c9685bab..fe255b2ab1 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -20,9 +20,9 @@ import { ExtensionDataRef, RouteRef, coreExtensionData, - createApiExtension, - createNavItemExtension, - createThemeExtension, + ApiBlueprint, + NavItemBlueprint, + ThemeBlueprint, useRouteRef, } from '@backstage/frontend-plugin-api'; import Box from '@material-ui/core/Box'; @@ -66,9 +66,9 @@ const getOutputColor = createOutputColorGenerator( [coreExtensionData.reactElement.id]: colors.green[500], [coreExtensionData.routePath.id]: colors.yellow[500], [coreExtensionData.routeRef.id]: colors.purple[500], - [createApiExtension.factoryDataRef.id]: colors.blue[500], - [createThemeExtension.themeDataRef.id]: colors.lime[500], - [createNavItemExtension.targetDataRef.id]: colors.orange[500], + [ApiBlueprint.dataRefs.factory.id]: colors.blue[500], + [ThemeBlueprint.dataRefs.theme.id]: colors.lime[500], + [NavItemBlueprint.dataRefs.target.id]: colors.orange[500], }, [ @@ -189,15 +189,26 @@ function OutputLink(props: { }) { const routeRef = props.node?.instance?.getData(coreExtensionData.routeRef); - const link = useRouteRef(routeRef as RouteRef); + try { + const link = useRouteRef(routeRef as RouteRef); - return ( - {props.dataRef.id}}> - - {link ? link : null} - - - ); + return ( + {props.dataRef.id}}> + + {link ? link : null} + + + ); + } catch (ex) { + // eslint-disable-next-line no-console + console.warn( + props.node?.spec.id + ? `Unable to generate output link for ${props.node.spec.id}` + : 'Unable to generate output link', + ex, + ); + return null; + } } function Output(props: { dataRef: ExtensionDataRef; node?: AppNode }) { @@ -323,11 +334,11 @@ function Extension(props: { node: AppNode; depth: number }) { const legendMap = { 'React Element': coreExtensionData.reactElement, - 'Utility API': createApiExtension.factoryDataRef, + 'Utility API': ApiBlueprint.dataRefs.factory, 'Route Path': coreExtensionData.routePath, 'Route Ref': coreExtensionData.routeRef, - 'Nav Target': createNavItemExtension.targetDataRef, - Theme: createThemeExtension.themeDataRef, + 'Nav Target': NavItemBlueprint.dataRefs.target, + Theme: ThemeBlueprint.dataRefs.theme, }; function Legend() { diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index 9a726ecc99..0e18b692e8 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -15,31 +15,37 @@ */ import { - createNavItemExtension, - createPageExtension, - createPlugin, + createFrontendPlugin, createRouteRef, + NavItemBlueprint, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import VisualizerIcon from '@material-ui/icons/Visibility'; import React from 'react'; const rootRouteRef = createRouteRef(); -const appVisualizerPage = createPageExtension({ - defaultPath: '/visualizer', - routeRef: rootRouteRef, - loader: () => - import('./components/AppVisualizerPage').then(m => ), +const appVisualizerPage = PageBlueprint.make({ + params: { + defaultPath: '/visualizer', + routeRef: rootRouteRef, + loader: () => + import('./components/AppVisualizerPage').then(m => ( + + )), + }, }); -export const appVisualizerNavItem = createNavItemExtension({ - title: 'Visualizer', - icon: VisualizerIcon, - routeRef: rootRouteRef, +export const appVisualizerNavItem = NavItemBlueprint.make({ + params: { + title: 'Visualizer', + icon: VisualizerIcon, + routeRef: rootRouteRef, + }, }); /** @public */ -export const visualizerPlugin = createPlugin({ +export const visualizerPlugin = createFrontendPlugin({ id: 'app-visualizer', extensions: [appVisualizerPage, appVisualizerNavItem], }); diff --git a/plugins/app/.eslintrc.js b/plugins/app/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/app/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md new file mode 100644 index 0000000000..2ff8607a13 --- /dev/null +++ b/plugins/app/CHANGELOG.md @@ -0,0 +1,57 @@ +# @backstage/plugin-app + +## 0.1.0-next.1 + +### Patch Changes + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## 0.1.0-next.0 + +### Minor Changes + +- 2bb9517: Introduce the `@backstage/plugin-app` package to hold all of the built-in extensions for easy consumption and overriding. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 diff --git a/plugins/app/README.md b/plugins/app/README.md new file mode 100644 index 0000000000..f54a8e0a4c --- /dev/null +++ b/plugins/app/README.md @@ -0,0 +1,9 @@ +# app + +Welcome to the app plugin! + +_This plugin was created through the Backstage CLI_ + +## 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 [/app](http://localhost:3000/app). diff --git a/plugins/app/api-report.md b/plugins/app/api-report.md new file mode 100644 index 0000000000..6f2e6e0463 --- /dev/null +++ b/plugins/app/api-report.md @@ -0,0 +1,657 @@ +## API Report File for "@backstage/plugin-app" + +> 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 { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { ComponentRef } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { ReactNode } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SignInPageProps } from '@backstage/core-plugin-api'; +import { TranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationResource } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const appPlugin: FrontendPlugin< + {}, + {}, + { + app: ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + root: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: true; + optional: false; + } + >; + }; + kind: undefined; + namespace: undefined; + name: undefined; + }>; + 'api:app/app-language': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'app-language'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'app/layout': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + nav: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: true; + optional: false; + } + >; + content: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: true; + optional: false; + } + >; + }; + kind: undefined; + namespace: undefined; + name: 'layout'; + }>; + 'app/nav': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + items: ExtensionInput< + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + { + singleton: false; + optional: false; + } + >; + logos: ExtensionInput< + ConfigurableExtensionDataRef< + { + logoIcon?: JSX.Element | undefined; + logoFull?: JSX.Element | undefined; + }, + 'core.nav-logo.logo-elements', + {} + >, + { + singleton: true; + optional: true; + } + >; + }; + kind: undefined; + namespace: undefined; + name: 'nav'; + }>; + 'app/root': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + router: ExtensionInput< + ConfigurableExtensionDataRef< + ComponentType<{ + children?: ReactNode; + }>, + 'app.router.wrapper', + {} + >, + { + singleton: true; + optional: true; + } + >; + signInPage: ExtensionInput< + ConfigurableExtensionDataRef< + ComponentType, + 'core.sign-in-page.component', + {} + >, + { + singleton: true; + optional: true; + } + >; + children: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: true; + optional: false; + } + >; + elements: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + } + >; + wrappers: ExtensionInput< + ConfigurableExtensionDataRef< + ComponentType<{ + children?: ReactNode; + }>, + 'app.root.wrapper', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: undefined; + namespace: undefined; + name: 'root'; + }>; + 'app/routes': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + routes: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: undefined; + namespace: undefined; + name: 'routes'; + }>; + 'api:app/app-theme': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: { + themes: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + namespace: undefined; + name: 'app-theme'; + }>; + 'theme:app/light': ExtensionDefinition<{ + kind: 'theme'; + namespace: undefined; + name: 'light'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef; + inputs: {}; + }>; + 'theme:app/dark': ExtensionDefinition<{ + kind: 'theme'; + namespace: undefined; + name: 'dark'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef; + inputs: {}; + }>; + 'api:app/components': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: { + components: ExtensionInput< + ConfigurableExtensionDataRef< + { + ref: ComponentRef; + impl: ComponentType; + }, + 'core.component.component', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + namespace: undefined; + name: 'components'; + }>; + 'api:app/icons': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: { + icons: ExtensionInput< + ConfigurableExtensionDataRef< + { + [x: string]: IconComponent_2; + }, + 'core.icons', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + namespace: undefined; + name: 'icons'; + }>; + 'api:app/feature-flags': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'feature-flags'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/translations': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: { + translations: ExtensionInput< + ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + namespace: undefined; + name: 'translations'; + }>; + 'app-root-element:app/oauth-request-dialog': ExtensionDefinition<{ + kind: 'app-root-element'; + namespace: undefined; + name: 'oauth-request-dialog'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'app-root-element:app/alert-display': ExtensionDefinition<{ + config: { + transientTimeoutMs: number; + anchorOrigin: { + horizontal: 'center' | 'left' | 'right'; + vertical: 'top' | 'bottom'; + }; + }; + configInput: { + anchorOrigin?: + | { + horizontal?: 'center' | 'left' | 'right' | undefined; + vertical?: 'top' | 'bottom' | undefined; + } + | undefined; + transientTimeoutMs?: number | undefined; + }; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'app-root-element'; + namespace: undefined; + name: 'alert-display'; + }>; + 'api:app/discovery': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'discovery'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/alert': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'alert'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/analytics': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'analytics'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/error': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'error'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/storage': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'storage'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/fetch': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'fetch'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/oauth-request': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'oauth-request'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/google-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'google-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/microsoft-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'microsoft-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/github-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'github-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/okta-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'okta-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/gitlab-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'gitlab-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/onelogin-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'onelogin-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/bitbucket-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'bitbucket-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/bitbucket-server-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'bitbucket-server-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/atlassian-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'atlassian-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/vmware-cloud-auth': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'vmware-cloud-auth'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:app/permission': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'permission'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + } +>; +export default appPlugin; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/app/catalog-info.yaml b/plugins/app/catalog-info.yaml new file mode 100644 index 0000000000..5bae243ea7 --- /dev/null +++ b/plugins/app/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app + title: '@backstage/plugin-app' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/app/package.json b/plugins/app/package.json new file mode 100644 index 0000000000..5912cc3781 --- /dev/null +++ b/plugins/app/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/plugin-app", + "version": "0.1.0-next.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/app" + }, + "backstage": { + "role": "frontend-plugin", + "pluginId": "app", + "pluginPackages": [ + "@backstage/plugin-app", + "@backstage/plugin-app-backend", + "@backstage/plugin-app-node" + ] + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", + "@backstage/theme": "workspace:^", + "@material-ui/core": "^4.9.13", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.61", + "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^15.0.0", + "@testing-library/user-event": "^14.0.0", + "msw": "^1.0.0", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/app/src/defaultApis.ts b/plugins/app/src/defaultApis.ts new file mode 100644 index 0000000000..fd46ecf596 --- /dev/null +++ b/plugins/app/src/defaultApis.ts @@ -0,0 +1,382 @@ +/* + * 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 { + AlertApiForwarder, + NoOpAnalyticsApi, + ErrorApiForwarder, + ErrorAlerter, + GoogleAuth, + GithubAuth, + OktaAuth, + GitlabAuth, + MicrosoftAuth, + BitbucketAuth, + BitbucketServerAuth, + OAuthRequestManager, + WebStorage, + UrlPatternDiscovery, + OneLoginAuth, + UnhandledErrorForwarder, + AtlassianAuth, + createFetchApi, + FetchMiddlewares, + VMwareCloudAuth, +} from '../../../packages/core-app-api/src/apis/implementations'; + +import { + createApiFactory, + alertApiRef, + analyticsApiRef, + errorApiRef, + discoveryApiRef, + fetchApiRef, + identityApiRef, + oauthRequestApiRef, + googleAuthApiRef, + githubAuthApiRef, + oktaAuthApiRef, + gitlabAuthApiRef, + microsoftAuthApiRef, + storageApiRef, + configApiRef, + oneloginAuthApiRef, + bitbucketAuthApiRef, + bitbucketServerAuthApiRef, + atlassianAuthApiRef, + vmwareCloudAuthApiRef, +} from '@backstage/core-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; +import { + permissionApiRef, + IdentityPermissionApi, +} from '@backstage/plugin-permission-react'; + +export const apis = [ + ApiBlueprint.make({ + name: 'discovery', + params: { + factory: createApiFactory({ + api: discoveryApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => + UrlPatternDiscovery.compile( + `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`, + ), + }), + }, + }), + ApiBlueprint.make({ + name: 'alert', + params: { + factory: createApiFactory({ + api: alertApiRef, + deps: {}, + factory: () => new AlertApiForwarder(), + }), + }, + }), + ApiBlueprint.make({ + name: 'analytics', + params: { + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => new NoOpAnalyticsApi(), + }), + }, + }), + ApiBlueprint.make({ + name: 'error', + params: { + factory: createApiFactory({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); + UnhandledErrorForwarder.forward(errorApi, { hidden: false }); + return errorApi; + }, + }), + }, + }), + ApiBlueprint.make({ + name: 'storage', + params: { + factory: createApiFactory({ + api: storageApiRef, + deps: { errorApi: errorApiRef }, + factory: ({ errorApi }) => WebStorage.create({ errorApi }), + }), + }, + }), + ApiBlueprint.make({ + name: 'fetch', + params: { + factory: createApiFactory({ + api: fetchApiRef, + deps: { + configApi: configApiRef, + identityApi: identityApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ configApi, identityApi, discoveryApi }) => { + return createFetchApi({ + middleware: [ + FetchMiddlewares.resolvePluginProtocol({ + discoveryApi, + }), + FetchMiddlewares.injectIdentityAuth({ + identityApi, + config: configApi, + }), + ], + }); + }, + }), + }, + }), + ApiBlueprint.make({ + name: 'oauth-request', + params: { + factory: createApiFactory({ + api: oauthRequestApiRef, + deps: {}, + factory: () => new OAuthRequestManager(), + }), + }, + }), + ApiBlueprint.make({ + name: 'google-auth', + params: { + factory: createApiFactory({ + api: googleAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GoogleAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'microsoft-auth', + params: { + factory: createApiFactory({ + api: microsoftAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + MicrosoftAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'github-auth', + params: { + factory: createApiFactory({ + api: githubAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GithubAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + defaultScopes: ['read:user'], + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'okta-auth', + params: { + factory: createApiFactory({ + api: oktaAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OktaAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'gitlab-auth', + params: { + factory: createApiFactory({ + api: gitlabAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GitlabAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'onelogin-auth', + params: { + factory: createApiFactory({ + api: oneloginAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OneLoginAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'bitbucket-auth', + params: { + factory: createApiFactory({ + api: bitbucketAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + BitbucketAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + defaultScopes: ['account'], + environment: configApi.getOptionalString('auth.environment'), + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'bitbucket-server-auth', + params: { + factory: createApiFactory({ + api: bitbucketServerAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + BitbucketServerAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + defaultScopes: ['REPO_READ'], + }), + }), + }, + }), + ApiBlueprint.make({ + name: 'atlassian-auth', + params: { + factory: createApiFactory({ + api: atlassianAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => { + return AtlassianAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }); + }, + }), + }, + }), + ApiBlueprint.make({ + name: 'vmware-cloud-auth', + params: { + factory: createApiFactory({ + api: vmwareCloudAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => { + return VMwareCloudAuth.create({ + configApi, + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }); + }, + }), + }, + }), + ApiBlueprint.make({ + name: 'permission', + params: { + factory: createApiFactory({ + api: permissionApiRef, + deps: { + discovery: discoveryApiRef, + identity: identityApiRef, + config: configApiRef, + }, + factory: ({ config, discovery, identity }) => + IdentityPermissionApi.create({ config, discovery, identity }), + }), + }, + }), +] as const; diff --git a/plugins/app/src/extensions/App.tsx b/plugins/app/src/extensions/App.tsx new file mode 100644 index 0000000000..7f4b3d5ee1 --- /dev/null +++ b/plugins/app/src/extensions/App.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + ExtensionBoundary, + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ApiProvider } from '../../../../packages/core-app-api/src'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppThemeProvider } from '../../../../packages/core-app-api/src/app/AppThemeProvider'; + +export const App = createExtension({ + attachTo: { id: 'root', input: 'app' }, + inputs: { + root: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory: ({ node, apis, inputs }) => { + return [ + coreExtensionData.reactElement( + + + + {inputs.root.get(coreExtensionData.reactElement)} + + + , + ), + ]; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts b/plugins/app/src/extensions/AppLanguageApi.ts similarity index 59% rename from packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts rename to plugins/app/src/extensions/AppLanguageApi.ts index 373dd10ead..7a6c10db96 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts +++ b/plugins/app/src/extensions/AppLanguageApi.ts @@ -15,10 +15,16 @@ */ // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { httpAuthServiceFactory as _httpAuthServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpAuth'; +import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi'; +import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha'; +import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api'; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpAuth` instead. - */ -export const httpAuthServiceFactory = _httpAuthServiceFactory; +export const AppLanguageApi = ApiBlueprint.make({ + name: 'app-language', + params: { + factory: createApiFactory( + appLanguageApiRef, + AppLanguageSelector.createWithStorage(), + ), + }, +}); diff --git a/packages/frontend-app-api/src/extensions/AppLayout.tsx b/plugins/app/src/extensions/AppLayout.tsx similarity index 63% rename from packages/frontend-app-api/src/extensions/AppLayout.tsx rename to plugins/app/src/extensions/AppLayout.tsx index 7be0029c25..3a0560a807 100644 --- a/packages/frontend-app-api/src/extensions/AppLayout.tsx +++ b/plugins/app/src/extensions/AppLayout.tsx @@ -23,34 +23,23 @@ import { import { SidebarPage } from '@backstage/core-components'; export const AppLayout = createExtension({ - namespace: 'app', name: 'layout', attachTo: { id: 'app/root', input: 'children' }, inputs: { - nav: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, + nav: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory: ({ inputs }) => [ + coreExtensionData.reactElement( + + {inputs.nav.get(coreExtensionData.reactElement)} + {inputs.content.get(coreExtensionData.reactElement)} + , ), - content: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, - ), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - {inputs.nav.output.element} - {inputs.content.output.element} - - ), - }; - }, + ], }); diff --git a/packages/frontend-app-api/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx similarity index 69% rename from packages/frontend-app-api/src/extensions/AppNav.tsx rename to plugins/app/src/extensions/AppNav.tsx index ffef1c7c61..3e4fcebf8c 100644 --- a/packages/frontend-app-api/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -20,8 +20,8 @@ import { coreExtensionData, createExtensionInput, useRouteRef, - createNavItemExtension, - createNavLogoExtension, + NavItemBlueprint, + NavLogoBlueprint, } from '@backstage/frontend-plugin-api'; import { makeStyles } from '@material-ui/core/styles'; import { @@ -33,9 +33,9 @@ import { SidebarItem, } from '@backstage/core-components'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoIcon from '../../../app/src/components/Root/LogoIcon'; +import LogoIcon from '../../../../packages/app/src/components/Root/LogoIcon'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoFull from '../../../app/src/components/Root/LogoFull'; +import LogoFull from '../../../../packages/app/src/components/Root/LogoFull'; const useSidebarLogoStyles = makeStyles({ root: { @@ -53,7 +53,7 @@ const useSidebarLogoStyles = makeStyles({ }); const SidebarLogo = ( - props: (typeof createNavLogoExtension.logoElementsDataRef)['T'], + props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'], ) => { const classes = useSidebarLogoStyles(); const { isOpen } = useSidebarOpenState(); @@ -70,7 +70,7 @@ const SidebarLogo = ( }; const SidebarNavItem = ( - props: (typeof createNavItemExtension.targetDataRef)['T'], + props: (typeof NavItemBlueprint.dataRefs.target)['T'], ) => { const { icon: Icon, title, routeRef } = props; const link = useRouteRef(routeRef); @@ -82,37 +82,30 @@ const SidebarNavItem = ( }; export const AppNav = createExtension({ - namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { - items: createExtensionInput({ - target: createNavItemExtension.targetDataRef, + items: createExtensionInput([NavItemBlueprint.dataRefs.target]), + logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], { + singleton: true, + optional: true, }), - logos: createExtensionInput( - { - elements: createNavLogoExtension.logoElementsDataRef, - }, - { - singleton: true, - optional: true, - }, + }, + output: [coreExtensionData.reactElement], + factory: ({ inputs }) => [ + coreExtensionData.reactElement( + + + + {inputs.items.map((item, index) => ( + + ))} + , ), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - return { - element: ( - - - - {inputs.items.map((item, index) => ( - - ))} - - ), - }; - }, + ], }); diff --git a/packages/frontend-app-api/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx similarity index 54% rename from packages/frontend-app-api/src/extensions/AppRoot.tsx rename to plugins/app/src/extensions/AppRoot.tsx index f3a962dd10..014f9a1354 100644 --- a/packages/frontend-app-api/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -19,81 +19,112 @@ import React, { Fragment, PropsWithChildren, ReactNode, - useContext, useState, } from 'react'; import { + AppRootWrapperBlueprint, + RouterBlueprint, + SignInPageBlueprint, coreExtensionData, - createAppRootWrapperExtension, + discoveryApiRef, + fetchApiRef, + errorApiRef, createExtension, createExtensionInput, - createRouterExtension, - createSignInPageExtension, + routeResolutionApiRef, } from '@backstage/frontend-plugin-api'; import { + DiscoveryApi, + ErrorApi, + FetchApi, IdentityApi, + ProfileInfo, SignInPageProps, configApiRef, + identityApiRef, useApi, } from '@backstage/core-plugin-api'; -import { InternalAppContext } from '../wiring/InternalAppContext'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp'; import { BrowserRouter } from 'react-router-dom'; -import { RouteTracker } from '../routing/RouteTracker'; -import { getBasePath } from '../routing/getBasePath'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath'; export const AppRoot = createExtension({ - namespace: 'app', name: 'root', attachTo: { id: 'app', input: 'root' }, inputs: { - router: createExtensionInput( - { component: createRouterExtension.componentDataRef }, - { singleton: true, optional: true }, - ), - signInPage: createExtensionInput( - { component: createSignInPageExtension.componentDataRef }, - { singleton: true, optional: true }, - ), - children: createExtensionInput( - { element: coreExtensionData.reactElement }, - { singleton: true }, - ), - elements: createExtensionInput({ - element: coreExtensionData.reactElement, + router: createExtensionInput([RouterBlueprint.dataRefs.component], { + singleton: true, + optional: true, }), - wrappers: createExtensionInput({ - component: createAppRootWrapperExtension.componentDataRef, + signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], { + singleton: true, + optional: true, }), + children: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + elements: createExtensionInput([coreExtensionData.reactElement]), + wrappers: createExtensionInput([ + AppRootWrapperBlueprint.dataRefs.component, + ]), }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { + output: [coreExtensionData.reactElement], + factory({ inputs, apis }) { + if (isProtectedApp()) { + const identityApi = apis.get(identityApiRef); + if (!identityApi) { + throw new Error('App requires an Identity API implementation'); + } + const appIdentityProxy = toAppIdentityProxy(identityApi); + const discoveryApi = apis.get(discoveryApiRef); + const errorApi = apis.get(errorApiRef); + const fetchApi = apis.get(fetchApiRef); + if (!discoveryApi || !errorApi || !fetchApi) { + throw new Error( + 'App is running in protected mode but missing required APIs', + ); + } + appIdentityProxy.enableCookieAuth({ + discoveryApi, + errorApi, + fetchApi, + }); + } + let content: React.ReactNode = ( <> {inputs.elements.map(el => ( - {el.output.element} + + {el.get(coreExtensionData.reactElement)} + ))} - {inputs.children.output.element} + {inputs.children.get(coreExtensionData.reactElement)} ); for (const wrapper of inputs.wrappers) { - content = {content}; + const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component); + content = {content}; } - return { - element: ( + return [ + coreExtensionData.reactElement( {content} - + , ), - }; + ]; }, }); @@ -121,6 +152,33 @@ function SignInPageWrapper({ return <>{children}; } +type AppIdentityProxy = IdentityApi & { + enableCookieAuth(ctx: { + errorApi: ErrorApi; + fetchApi: FetchApi; + discoveryApi: DiscoveryApi; + }): void; + setTarget( + impl: IdentityApi & /* backwards compat stuff */ { + getUserId?(): string; + getIdToken?(): Promise; + getProfile?(): ProfileInfo; + }, + options: { signOutTargetUrl: string }, + ): void; +}; + +function toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy { + if (!('enableCookieAuth' in identityApi)) { + throw new Error('Unexpected Identity API implementation'); + } + return identityApi as AppIdentityProxy; +} + +type RouteResolverProxy = { + getRouteObjects(): any[]; +}; + /** * Props for the {@link AppRouter} component. * @public @@ -155,12 +213,17 @@ export function AppRouter(props: AppRouterProps) { } = props; const configApi = useApi(configApiRef); + const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef)); + const routeResolutionsApi = useApi(routeResolutionApiRef); const basePath = getBasePath(configApi); - const internalAppContext = useContext(InternalAppContext); - if (!internalAppContext) { - throw new Error('AppRouter must be rendered within the AppProvider'); + + // TODO: Private access for now, probably replace with path -> node lookup method on the API + if (!('getRouteObjects' in routeResolutionsApi)) { + throw new Error('Unexpected route resolution API implementation'); } - const { routeObjects, appIdentityProxy } = internalAppContext; + const routeObjects = ( + routeResolutionsApi as RouteResolverProxy + ).getRouteObjects(); // If the app hasn't configured a sign-in page, we just continue as guest. if (!SignInPageComponent) { diff --git a/packages/frontend-app-api/src/extensions/AppRoutes.tsx b/plugins/app/src/extensions/AppRoutes.tsx similarity index 77% rename from packages/frontend-app-api/src/extensions/AppRoutes.tsx rename to plugins/app/src/extensions/AppRoutes.tsx index 38a9291755..5880d6182e 100644 --- a/packages/frontend-app-api/src/extensions/AppRoutes.tsx +++ b/plugins/app/src/extensions/AppRoutes.tsx @@ -25,19 +25,16 @@ import { import { useRoutes } from 'react-router-dom'; export const AppRoutes = createExtension({ - namespace: 'app', name: 'routes', attachTo: { id: 'app/layout', input: 'content' }, inputs: { - routes: createExtensionInput({ - path: coreExtensionData.routePath, - ref: coreExtensionData.routeRef.optional(), - element: coreExtensionData.reactElement, - }), - }, - output: { - element: coreExtensionData.reactElement, + routes: createExtensionInput([ + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + coreExtensionData.reactElement, + ]), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { const Routes = () => { const NotFoundErrorPage = useComponentRef( @@ -46,8 +43,8 @@ export const AppRoutes = createExtension({ const element = useRoutes([ ...inputs.routes.map(route => ({ - path: `${route.output.path}/*`, - element: route.output.element, + path: `${route.get(coreExtensionData.routePath)}/*`, + element: route.get(coreExtensionData.reactElement), })), { path: '*', @@ -58,8 +55,6 @@ export const AppRoutes = createExtension({ return element; }; - return { - element: , - }; + return [coreExtensionData.reactElement()]; }, }); diff --git a/plugins/app/src/extensions/AppThemeApi.tsx b/plugins/app/src/extensions/AppThemeApi.tsx new file mode 100644 index 0000000000..a31e29e58b --- /dev/null +++ b/plugins/app/src/extensions/AppThemeApi.tsx @@ -0,0 +1,84 @@ +/* + * 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 { + UnifiedThemeProvider, + themes as builtinThemes, +} from '@backstage/theme'; +import DarkIcon from '@material-ui/icons/Brightness2'; +import LightIcon from '@material-ui/icons/WbSunny'; +import { + createExtensionInput, + ThemeBlueprint, + ApiBlueprint, + createApiFactory, + appThemeApiRef, +} from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppThemeSelector } from '../../../../packages/core-app-api/src/apis/implementations'; + +/** + * Contains the themes installed into the app. + */ +export const AppThemeApi = ApiBlueprint.makeWithOverrides({ + name: 'app-theme', + inputs: { + themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], { + replaces: [{ id: 'app', input: 'themes' }], + }), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + factory: createApiFactory( + appThemeApiRef, + AppThemeSelector.createWithStorage( + inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), + ), + ), + }); + }, +}); + +export const LightTheme = ThemeBlueprint.make({ + name: 'light', + params: { + theme: { + id: 'light', + title: 'Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, +}); + +export const DarkTheme = ThemeBlueprint.make({ + name: 'dark', + params: { + theme: { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + icon: , + Provider: ({ children }) => ( + + ), + }, + }, +}); diff --git a/plugins/app/src/extensions/ComponentsApi.tsx b/plugins/app/src/extensions/ComponentsApi.tsx new file mode 100644 index 0000000000..b578429253 --- /dev/null +++ b/plugins/app/src/extensions/ComponentsApi.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createComponentExtension, + createExtensionInput, + ApiBlueprint, + createApiFactory, + componentsApiRef, +} from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi'; + +/** + * Contains the shareable components installed into the app. + */ +export const ComponentsApi = ApiBlueprint.makeWithOverrides({ + name: 'components', + inputs: { + components: createExtensionInput( + [createComponentExtension.componentDataRef], + { replaces: [{ id: 'app', input: 'components' }] }, + ), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + factory: createApiFactory( + componentsApiRef, + DefaultComponentsApi.fromComponents( + inputs.components.map(i => + i.get(createComponentExtension.componentDataRef), + ), + ), + ), + }); + }, +}); diff --git a/plugins/app/src/extensions/FeatureFlagsApi.ts b/plugins/app/src/extensions/FeatureFlagsApi.ts new file mode 100644 index 0000000000..40a44d1348 --- /dev/null +++ b/plugins/app/src/extensions/FeatureFlagsApi.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 { + ApiBlueprint, + createApiFactory, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { LocalStorageFeatureFlags } from '../../../../packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags'; + +/** + * Contains the shareable icons installed into the app. + */ +export const FeatureFlagsApi = ApiBlueprint.make({ + name: 'feature-flags', + params: { + // TODO: properly discovery feature flags, maybe rework the whole thing + factory: createApiFactory({ + api: featureFlagsApiRef, + deps: {}, + factory: () => new LocalStorageFeatureFlags(), + }), + }, +}); diff --git a/plugins/app/src/extensions/IconsApi.ts b/plugins/app/src/extensions/IconsApi.ts new file mode 100644 index 0000000000..e73a74132a --- /dev/null +++ b/plugins/app/src/extensions/IconsApi.ts @@ -0,0 +1,51 @@ +/* + * 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 { + createExtensionInput, + IconBundleBlueprint, + ApiBlueprint, + createApiFactory, + iconsApiRef, +} from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { DefaultIconsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/IconsApi'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { icons as defaultIcons } from '../../../../packages/app-defaults/src/defaults'; + +/** + * Contains the shareable icons installed into the app. + */ +export const IconsApi = ApiBlueprint.makeWithOverrides({ + name: 'icons', + inputs: { + icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons], { + replaces: [{ id: 'app', input: 'icons' }], + }), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + factory: createApiFactory( + iconsApiRef, + new DefaultIconsApi( + inputs.icons + .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) + .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), + ), + ), + }); + }, +}); diff --git a/plugins/app/src/extensions/TranslationsApi.tsx b/plugins/app/src/extensions/TranslationsApi.tsx new file mode 100644 index 0000000000..ed7ab1abdb --- /dev/null +++ b/plugins/app/src/extensions/TranslationsApi.tsx @@ -0,0 +1,56 @@ +/* + * 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, + TranslationBlueprint, + createApiFactory, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { + appLanguageApiRef, + translationApiRef, +} from '@backstage/core-plugin-api/alpha'; + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { I18nextTranslationApi } from '../../../../packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; + +/** + * Contains translations that are installed in the app. + */ +export const TranslationsApi = ApiBlueprint.makeWithOverrides({ + name: 'translations', + inputs: { + translations: createExtensionInput( + [TranslationBlueprint.dataRefs.translation], + { replaces: [{ id: 'app', input: 'translations' }] }, + ), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + factory: createApiFactory({ + api: translationApiRef, + deps: { languageApi: appLanguageApiRef }, + factory: ({ languageApi }) => + I18nextTranslationApi.create({ + languageApi, + resources: inputs.translations.map(i => + i.get(TranslationBlueprint.dataRefs.translation), + ), + }), + }), + }); + }, +}); diff --git a/packages/frontend-app-api/src/extensions/components.tsx b/plugins/app/src/extensions/components.tsx similarity index 92% rename from packages/frontend-app-api/src/extensions/components.tsx rename to plugins/app/src/extensions/components.tsx index 4f6574ab3e..829cb99ea2 100644 --- a/packages/frontend-app-api/src/extensions/components.tsx +++ b/plugins/app/src/extensions/components.tsx @@ -24,8 +24,7 @@ import { } from '@backstage/frontend-plugin-api'; import { ErrorPanel } from '@backstage/core-components'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { components as defaultComponents } from '../../../app-defaults/src/defaults'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { components as defaultComponents } from '../../../../packages/app-defaults/src/defaults'; export const DefaultProgressComponent = createComponentExtension({ ref: coreComponentRefs.progress, diff --git a/plugins/app/src/extensions/elements.tsx b/plugins/app/src/extensions/elements.tsx new file mode 100644 index 0000000000..bbb6374ebe --- /dev/null +++ b/plugins/app/src/extensions/elements.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +import { AppRootElementBlueprint } from '@backstage/frontend-plugin-api'; +import React from 'react'; + +export const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({ + name: 'oauth-request-dialog', + params: { + element: , + }, +}); + +export const alertDisplayAppRootElement = + AppRootElementBlueprint.makeWithOverrides({ + name: 'alert-display', + config: { + schema: { + transientTimeoutMs: z => z.number().default(5000), + anchorOrigin: z => + z + .object({ + vertical: z.enum(['top', 'bottom']).default('top'), + horizontal: z.enum(['left', 'center', 'right']).default('center'), + }) + .default({}), + }, + }, + factory: (originalFactory, { config }) => { + return originalFactory({ + element: () => , + }); + }, + }); diff --git a/plugins/app/src/extensions/index.ts b/plugins/app/src/extensions/index.ts new file mode 100644 index 0000000000..157f7ddb62 --- /dev/null +++ b/plugins/app/src/extensions/index.ts @@ -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. + */ +export { App } from './App'; +export { AppLanguageApi } from './AppLanguageApi'; +export { AppLayout } from './AppLayout'; +export { AppNav } from './AppNav'; +export { AppRoot } from './AppRoot'; +export { AppRoutes } from './AppRoutes'; +export { AppThemeApi, DarkTheme, LightTheme } from './AppThemeApi'; +export { ComponentsApi } from './ComponentsApi'; +export { IconsApi } from './IconsApi'; +export { FeatureFlagsApi } from './FeatureFlagsApi'; +export { TranslationsApi } from './TranslationsApi'; +export { + DefaultProgressComponent, + DefaultErrorBoundaryComponent, + DefaultNotFoundErrorPageComponent, +} from './components'; +export { + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, +} from './elements'; diff --git a/packages/backend-app-api/src/services/implementations/auth/index.ts b/plugins/app/src/index.ts similarity index 91% rename from packages/backend-app-api/src/services/implementations/auth/index.ts rename to plugins/app/src/index.ts index 1b55d46a83..24f65df618 100644 --- a/packages/backend-app-api/src/services/implementations/auth/index.ts +++ b/plugins/app/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { authServiceFactory } from './authServiceFactory'; +export { appPlugin as default } from './plugin'; diff --git a/packages/backend-app-api/src/logging/index.ts b/plugins/app/src/plugin.test.ts similarity index 75% rename from packages/backend-app-api/src/logging/index.ts rename to plugins/app/src/plugin.test.ts index 14fe33f898..80588710a5 100644 --- a/packages/backend-app-api/src/logging/index.ts +++ b/plugins/app/src/plugin.test.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. @@ -13,6 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { appPlugin } from './plugin'; -export { WinstonLogger } from './WinstonLogger'; -export type { WinstonLoggerOptions } from './WinstonLogger'; +describe('app', () => { + it('should export plugin', () => { + expect(appPlugin).toBeDefined(); + }); +}); diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts new file mode 100644 index 0000000000..ff0c69faec --- /dev/null +++ b/plugins/app/src/plugin.ts @@ -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 { createFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { + App, + AppLanguageApi, + AppLayout, + AppNav, + AppRoot, + AppRoutes, + AppThemeApi, + DarkTheme, + LightTheme, + ComponentsApi, + IconsApi, + FeatureFlagsApi, + TranslationsApi, + DefaultProgressComponent, + DefaultNotFoundErrorPageComponent, + DefaultErrorBoundaryComponent, + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, +} from './extensions'; +import { apis } from './defaultApis'; + +/** @public */ +export const appPlugin = createFrontendPlugin({ + id: 'app', + extensions: [ + ...apis, + App, + AppLanguageApi, + AppLayout, + AppNav, + AppRoot, + AppRoutes, + AppThemeApi, + DarkTheme, + LightTheme, + ComponentsApi, + IconsApi, + FeatureFlagsApi, + TranslationsApi, + DefaultProgressComponent, + DefaultNotFoundErrorPageComponent, + DefaultErrorBoundaryComponent, + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, + ], +}); diff --git a/plugins/app/src/setupTests.ts b/plugins/app/src/setupTests.ts new file mode 100644 index 0000000000..658016ffdd --- /dev/null +++ b/plugins/app/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * 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 '@testing-library/jest-dom'; diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index e2014d6749..294ab9204d 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.2.4-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/api-report.md b/plugins/auth-backend-module-atlassian-provider/api-report.md index 080206cdf7..7abdf7ee70 100644 --- a/plugins/auth-backend-module-atlassian-provider/api-report.md +++ b/plugins/auth-backend-module-atlassian-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -25,6 +25,6 @@ export namespace atlassianSignInResolvers { } // @public (undocumented) -const authModuleAtlassianProvider: BackendFeatureCompat; +const authModuleAtlassianProvider: BackendFeature; export default authModuleAtlassianProvider; ``` diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 995b980124..105e69150b 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.2.4-next.2", + "version": "0.3.0-next.1", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index ce99594759..ef6008aa90 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.2.0-next.1 + +### Patch Changes + +- 8d1fb8d: Throw correct error when email is missing from the claims +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- ecbc47e: Fix a bug where the signer was checked from the payload instead of the header +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.1.15-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/api-report.md b/plugins/auth-backend-module-aws-alb-provider/api-report.md index 7f4531419a..b3a9ce4517 100644 --- a/plugins/auth-backend-module-aws-alb-provider/api-report.md +++ b/plugins/auth-backend-module-aws-alb-provider/api-report.md @@ -5,7 +5,7 @@ ```ts /// -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { JWTHeaderParameters } from 'jose'; import { KeyObject } from 'crypto'; import type { PassportProfile } from '@backstage/plugin-auth-node/'; @@ -13,7 +13,7 @@ import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleAwsAlbProvider: BackendFeatureCompat; +const authModuleAwsAlbProvider: BackendFeature; export { authModuleAwsAlbProvider }; export default authModuleAwsAlbProvider; @@ -21,6 +21,7 @@ export default authModuleAwsAlbProvider; export const awsAlbAuthenticator: ProxyAuthenticator< { issuer: string; + signer: string | undefined; getKey: (header: JWTHeaderParameters) => Promise; }, AwsAlbResult, diff --git a/plugins/auth-backend-module-aws-alb-provider/config.d.ts b/plugins/auth-backend-module-aws-alb-provider/config.d.ts index 5a3e90cf6b..d76ae5bada 100644 --- a/plugins/auth-backend-module-aws-alb-provider/config.d.ts +++ b/plugins/auth-backend-module-aws-alb-provider/config.d.ts @@ -19,7 +19,25 @@ export interface Config { providers?: { /** @visibility frontend */ awsalb?: { - issuer?: string; + /** + * The issuer IdP URL that was configured in your ALB; this corresponds + * to the `iss` claim in your tokens. + * + * @example https://example.okta.com/oauth2/default + */ + issuer: string; + /** + * The ARN of the ALB that signs the tokens; this corresponds to the + * `signer` claim in your tokens. + * + * @example arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456 + */ + signer?: string; + /** + * The AWS region where the ALB is located. + * + * @example us-east-2 + */ region: string; signIn?: { resolvers: Array< diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 2ddace0546..de5fb31298 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.1.15-next.2", + "version": "0.2.0-next.1", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts index e4700d10dd..73f859df52 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts @@ -21,7 +21,7 @@ import { ALB_JWT_HEADER, awsAlbAuthenticator, } from './authenticator'; -import { Config } from '@backstage/config'; +import { ConfigReader } from '@backstage/config'; import { AuthenticationError } from '@backstage/errors'; describe('AwsAlbProvider', () => { @@ -77,7 +77,7 @@ describe('AwsAlbProvider', () => { beforeEach(async () => { mockJwt = await new SignJWT(mockClaims) - .setProtectedHeader({ alg: 'HS256' }) + .setProtectedHeader({ alg: 'HS256', signer: 'SIGNER_ARN' }) .sign(signingKey); }); @@ -87,6 +87,7 @@ describe('AwsAlbProvider', () => { { req: mockRequest }, { issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', getKey: jest.fn().mockResolvedValue(signingKey), }, ); @@ -114,12 +115,13 @@ describe('AwsAlbProvider', () => { }); }); }); + describe('should fail when', () => { it('Access token is missing', async () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithoutAccessToken }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow(AuthenticationError); }); @@ -128,7 +130,7 @@ describe('AwsAlbProvider', () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithoutJwt }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow(AuthenticationError); }); @@ -137,13 +139,41 @@ describe('AwsAlbProvider', () => { await expect( awsAlbAuthenticator.authenticate( { req: mockRequestWithInvalidJwt }, - { issuer: 'ISSUER_URL', getKey: jest.fn() }, + { issuer: 'ISSUER_URL', signer: 'SIGNER_ARN', getKey: jest.fn() }, ), ).rejects.toThrow( 'Exception occurred during JWT processing: JWSInvalid: Invalid Compact JWS', ); }); + it('Email is missing', async () => { + const jwt = await new SignJWT({ ...mockClaims, email: undefined }) + .setProtectedHeader({ alg: 'HS256', signer: 'SIGNER_ARN' }) + .sign(signingKey); + const req = { + header: jest.fn(name => { + if (name === ALB_JWT_HEADER) { + return jwt; + } else if (name === ALB_ACCESS_TOKEN_HEADER) { + return mockAccessToken; + } + return undefined; + }), + } as unknown as express.Request; + await expect( + awsAlbAuthenticator.authenticate( + { req }, + { + issuer: 'ISSUER_URL', + signer: undefined, + getKey: jest.fn().mockResolvedValue(signingKey), + }, + ), + ).rejects.toThrow( + 'Exception occurred during JWT processing: AuthenticationError: Missing email in the JWT token', + ); + }); + it('issuer is missing', async () => { const jwt = await new SignJWT({}) .setProtectedHeader({ alg: 'HS256' }) @@ -164,6 +194,7 @@ describe('AwsAlbProvider', () => { { req }, { issuer: 'ISSUER_URL', + signer: undefined, getKey: jest.fn().mockResolvedValue(signingKey), }, ), @@ -192,6 +223,36 @@ describe('AwsAlbProvider', () => { { req }, { issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', + getKey: jest.fn().mockResolvedValue(signingKey), + }, + ), + ).rejects.toThrow( + 'Exception occurred during JWT processing: AuthenticationError: Issuer mismatch on JWT token', + ); + }); + + it('signer is invalid', async () => { + const jwt = await new SignJWT({}) + .setProtectedHeader({ alg: 'HS256', signer: 'INVALID_SIGNER_ARN' }) + .sign(signingKey); + const req = { + header: jest.fn(name => { + if (name === ALB_JWT_HEADER) { + return jwt; + } else if (name === ALB_ACCESS_TOKEN_HEADER) { + return mockAccessToken; + } + return undefined; + }), + } as unknown as express.Request; + + await expect( + awsAlbAuthenticator.authenticate( + { req }, + { + issuer: 'ISSUER_URL', + signer: 'SIGNER_ARN', getKey: jest.fn().mockResolvedValue(signingKey), }, ), @@ -200,15 +261,14 @@ describe('AwsAlbProvider', () => { ); }); }); + describe('should initialize', () => { it('with default options', async () => { const config = { - config: { - getString: jest - .fn() - .mockReturnValueOnce('ISSUER_URL') - .mockReturnValueOnce('TEST_REGION'), - } as unknown as Config, + config: new ConfigReader({ + issuer: 'ISSUER_URL', + region: 'TEST_REGION', + }), }; expect(awsAlbAuthenticator.initialize(config)).toEqual({ diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts index e7e0a7d76d..2e7fe1cd26 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts @@ -15,11 +15,11 @@ */ import { AuthenticationError } from '@backstage/errors'; -import { AwsAlbClaims, AwsAlbResult } from './types'; +import { AwsAlbClaims, AwsAlbProtectedHeader, AwsAlbResult } from './types'; import { jwtVerify } from 'jose'; import { - PassportProfile, createProxyAuthenticator, + PassportProfile, } from '@backstage/plugin-auth-node'; import NodeCache from 'node-cache'; import { makeProfileInfo, provisionKeyCache } from './helpers'; @@ -36,12 +36,13 @@ export const awsAlbAuthenticator = createProxyAuthenticator({ }, initialize({ config }) { const issuer = config.getString('issuer'); + const signer = config.getOptionalString('signer'); const region = config.getString('region'); const keyCache = new NodeCache({ stdTTL: 3600 }); const getKey = provisionKeyCache(region, keyCache); - return { issuer, getKey }; + return { issuer, signer, getKey }; }, - async authenticate({ req }, { issuer, getKey }) { + async authenticate({ req }, { issuer, signer, getKey }) { const jwt = req.header(ALB_JWT_HEADER); const accessToken = req.header(ALB_ACCESS_TOKEN_HEADER); @@ -59,10 +60,17 @@ export const awsAlbAuthenticator = createProxyAuthenticator({ try { const verifyResult = await jwtVerify(jwt, getKey); + const header = verifyResult.protectedHeader as AwsAlbProtectedHeader; const claims = verifyResult.payload as AwsAlbClaims; - if (issuer && claims?.iss !== issuer) { + if (claims?.iss !== issuer) { throw new AuthenticationError('Issuer mismatch on JWT token'); + } else if (signer && header?.signer !== signer) { + throw new AuthenticationError('Signer mismatch on JWT token'); + } + + if (!claims.email) { + throw new AuthenticationError(`Missing email in the JWT token`); } const fullProfile: PassportProfile = { diff --git a/plugins/auth-backend-module-aws-alb-provider/src/types.ts b/plugins/auth-backend-module-aws-alb-provider/src/types.ts index c45640851c..b4c792a5f9 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/types.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/types.ts @@ -39,3 +39,9 @@ export type AwsAlbClaims = { exp: number; iss: string; }; +/** + * @internal + */ +export type AwsAlbProtectedHeader = { + signer?: string; +}; diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 471f86842e..cb1f2da1fb 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.1.6-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/api-report.md b/plugins/auth-backend-module-azure-easyauth-provider/api-report.md index cb198f54db..9d337e3f34 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/api-report.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/api-report.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 { Profile } from 'passport'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleAzureEasyAuthProvider: BackendFeatureCompat; +const authModuleAzureEasyAuthProvider: BackendFeature; export default authModuleAzureEasyAuthProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index f48e7ce40b..9688d88308 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.1.6-next.2", + "version": "0.2.0-next.1", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index e70bd7f8ee..92484e03fe 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.6-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/api-report.md b/plugins/auth-backend-module-bitbucket-provider/api-report.md index 6b71f62c69..ceb4141fd9 100644 --- a/plugins/auth-backend-module-bitbucket-provider/api-report.md +++ b/plugins/auth-backend-module-bitbucket-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleBitbucketProvider: BackendFeatureCompat; +const authModuleBitbucketProvider: BackendFeature; export default authModuleBitbucketProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index ae9692bfe8..e2385ca6ed 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.1.6-next.2", + "version": "0.2.0-next.1", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 493b57d26a..1ed55e9d69 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.1.6-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/api-report.md b/plugins/auth-backend-module-cloudflare-access-provider/api-report.md index b728f52046..1547190d39 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/api-report.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/api-report.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 { CacheService } from '@backstage/backend-plugin-api'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public -const authModuleCloudflareAccessProvider: BackendFeatureCompat; +const authModuleCloudflareAccessProvider: BackendFeature; export default authModuleCloudflareAccessProvider; // @public diff --git a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts index 1300eb8c57..21b839b8d7 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts @@ -27,6 +27,8 @@ export interface Config { token: string; subject: string; }>; + jwtHeaderName?: string; + authorizationCookieName?: string; signIn?: { resolvers: Array< | { resolver: 'emailLocalPartMatchingUserEntityName' } diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index c388ead3cb..b40928a4bf 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.1.6-next.2", + "version": "0.3.0-next.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts index 9efab07ba5..07e9860d98 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.test.ts @@ -239,4 +239,89 @@ describe('helpers', () => { token: token, }); }); + + it('works for regular tokens, through jwtHeaderName header', async () => { + jest.useFakeTimers({ + now: 1600000004000, + }); + + const helper = AuthHelper.fromConfig( + new ConfigReader({ + teamName: 'mock-team', + jwtHeaderName: 'X-Auth-Token', + }), + { cache }, + ); + const token = await tokenFactory.userToken(); + const request = createRequest({ + headers: { ['X-Auth-Token']: token }, + }); + + const expected = { + cfIdentity: { + email: 'hello@example.com', + groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }], + id: '1234567890', + name: 'User Name', + }, + claims: { + iss: `https://mock-team.cloudflareaccess.com`, + sub: '1234567890', + name: 'User Name', + iat: 1600000000, + exp: 1600000005, + }, + expiresInSeconds: 5, + }; + + await expect(helper.authenticate(request)).resolves.toEqual({ + ...expected, + token: token, + }); + expect(cache.set).toHaveBeenCalledTimes(1); + expect(cache.set.mock.calls[0][0]).toBe( + 'providers/cloudflare-access/profile-v1/1234567890', + ); + expect(JSON.parse(cache.set.mock.calls[0][1] as string)).toEqual(expected); + }); + + it('works for regular tokens, through authorizationCookieName cookie name', async () => { + jest.useFakeTimers({ + now: 1600000004000, + }); + + const helper = AuthHelper.fromConfig( + new ConfigReader({ + teamName: 'mock-team', + authorizationCookieName: 'CF_Auth', + }), + { cache }, + ); + const token = await tokenFactory.userToken(); + const request = createRequest({ + cookies: { CF_Auth: token }, + }); + + const expected = { + cfIdentity: { + email: 'hello@example.com', + groups: [{ id: '123', email: 'foo@bar.com', name: 'foo' }], + id: '1234567890', + name: 'User Name', + }, + claims: { + iss: `https://mock-team.cloudflareaccess.com`, + sub: '1234567890', + name: 'User Name', + iat: 1600000000, + exp: 1600000005, + }, + expiresInSeconds: 5, + }; + + await expect(helper.authenticate(request)).resolves.toEqual({ + ...expected, + token: token, + }); + }); }); diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index c1a14ae6b4..3077258dd6 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -40,6 +40,10 @@ export class AuthHelper { options?: { cache?: CacheService }, ): AuthHelper { const teamName = config.getString('teamName'); + const jwtHeaderName = + config.getOptionalString('jwtHeaderName') ?? CF_JWT_HEADER; + const authorizationCookieName = + config.getOptionalString('authorizationCookieName') ?? COOKIE_AUTH_NAME; const serviceTokens = ( config.getOptionalConfigArray('serviceTokens') ?? [] )?.map(cfg => { @@ -53,12 +57,21 @@ export class AuthHelper { new URL(`https://${teamName}.cloudflareaccess.com/cdn-cgi/access/certs`), ); - return new AuthHelper(teamName, serviceTokens, keySet, options?.cache); + return new AuthHelper( + teamName, + serviceTokens, + jwtHeaderName, + authorizationCookieName, + keySet, + options?.cache, + ); } private constructor( private readonly teamName: string, private readonly serviceTokens: ServiceToken[], + private readonly jwtHeaderName: string, + private readonly authorizationCookieName: string, private readonly keySet: ReturnType, private readonly cache?: CacheService, ) {} @@ -66,15 +79,16 @@ export class AuthHelper { async authenticate(req: express.Request): Promise { // JWTs generated by Access are available in a request header as // Cf-Access-Jwt-Assertion and as cookies as CF_Authorization. - let jwt = req.header(CF_JWT_HEADER); + let jwt = req.header(this.jwtHeaderName); if (!jwt) { - jwt = req.cookies.CF_Authorization; + jwt = req.cookies[this.authorizationCookieName]; } if (!jwt) { // Only throw if both are not provided by Cloudflare Access since either // can be used. throw new AuthenticationError( - `Missing ${CF_JWT_HEADER} from Cloudflare Access`, + `Missing ${this.jwtHeaderName} and + ${this.authorizationCookieName} from Cloudflare Access`, ); } @@ -155,8 +169,8 @@ export class AuthHelper { ): Promise { const headers = new Headers(); // set both headers just the way inbound responses are set - headers.set(CF_JWT_HEADER, jwt); - headers.set('cookie', `${COOKIE_AUTH_NAME}=${jwt}`); + headers.set(this.jwtHeaderName, jwt); + headers.set('cookie', `${this.authorizationCookieName}=${jwt}`); try { const res = await fetch( `https://${this.teamName}.cloudflareaccess.com/cdn-cgi/access/get-identity`, diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 3b4f24a64a..32de67cf73 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 + +## 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 + ## 0.2.18-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/api-report.md b/plugins/auth-backend-module-gcp-iap-provider/api-report.md index ccde0b8809..1476694f99 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/api-report.md +++ b/plugins/auth-backend-module-gcp-iap-provider/api-report.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 { JsonPrimitive } from '@backstage/types'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleGcpIapProvider: BackendFeatureCompat; +const authModuleGcpIapProvider: BackendFeature; export default authModuleGcpIapProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-gcp-iap-provider/config.d.ts b/plugins/auth-backend-module-gcp-iap-provider/config.d.ts index 452f9819c3..4ca426d10e 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/config.d.ts +++ b/plugins/auth-backend-module-gcp-iap-provider/config.d.ts @@ -21,26 +21,24 @@ export interface Config { * Configuration for the Google Cloud Platform Identity-Aware Proxy (IAP) auth provider. */ gcpIap?: { - [authEnv: string]: { - /** - * The audience to use when validating incoming JWT tokens. - * See https://backstage.io/docs/auth/google/gcp-iap-auth - */ - audience: string; + /** + * The audience to use when validating incoming JWT tokens. + * See https://backstage.io/docs/auth/google/gcp-iap-auth + */ + audience: string; - /** - * The name of the header to read the JWT token from, defaults to `'x-goog-iap-jwt-assertion'`. - */ - jwtHeader?: string; + /** + * The name of the header to read the JWT token from, defaults to `'x-goog-iap-jwt-assertion'`. + */ + jwtHeader?: string; - signIn?: { - resolvers: Array< - | { resolver: 'emailMatchingUserEntityAnnotation' } - | { resolver: 'idMatchingUserEntityAnnotation' } - | { resolver: 'emailLocalPartMatchingUserEntityName' } - | { resolver: 'emailMatchingUserEntityProfileEmail' } - >; - }; + signIn?: { + resolvers: Array< + | { resolver: 'emailMatchingUserEntityAnnotation' } + | { resolver: 'idMatchingUserEntityAnnotation' } + | { resolver: 'emailLocalPartMatchingUserEntityName' } + | { resolver: 'emailMatchingUserEntityProfileEmail' } + >; }; }; }; diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 20b3f88606..94e52df683 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.2.18-next.2", + "version": "0.3.0-next.1", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 83db20aaa5..b898b8b23f 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.20-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/api-report.md b/plugins/auth-backend-module-github-provider/api-report.md index ed8b615033..222305adfd 100644 --- a/plugins/auth-backend-module-github-provider/api-report.md +++ b/plugins/auth-backend-module-github-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleGithubProvider: BackendFeatureCompat; +const authModuleGithubProvider: BackendFeature; export default authModuleGithubProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index e773518782..08e3acaf8f 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.1.20-next.2", + "version": "0.2.0-next.1", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 8f502140b7..8cf6bbc41e 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.20-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/api-report.md b/plugins/auth-backend-module-gitlab-provider/api-report.md index 664d04e856..44c5ffb214 100644 --- a/plugins/auth-backend-module-gitlab-provider/api-report.md +++ b/plugins/auth-backend-module-gitlab-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleGitlabProvider: BackendFeatureCompat; +const authModuleGitlabProvider: BackendFeature; export default authModuleGitlabProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 88fd39a53b..dc562c16e8 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.1.20-next.2", + "version": "0.2.0-next.1", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 64d59dc5aa..72c422b3bc 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.20-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/api-report.md b/plugins/auth-backend-module-google-provider/api-report.md index 5c6735c117..eeefb8e837 100644 --- a/plugins/auth-backend-module-google-provider/api-report.md +++ b/plugins/auth-backend-module-google-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleGoogleProvider: BackendFeatureCompat; +const authModuleGoogleProvider: BackendFeature; export default authModuleGoogleProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index ed2e536396..3f4305d8e5 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.1.20-next.2", + "version": "0.2.0-next.1", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 4c5df385c7..69c7e94a3e 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.1.9-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/api-report.md b/plugins/auth-backend-module-guest-provider/api-report.md index 85a067affa..b0ba5386a2 100644 --- a/plugins/auth-backend-module-guest-provider/api-report.md +++ b/plugins/auth-backend-module-guest-provider/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 (undocumented) -const authModuleGuestProvider: BackendFeatureCompat; +const authModuleGuestProvider: BackendFeature; export default authModuleGuestProvider; ``` diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index d41612d32a..d46324f4de 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.9-next.2", + "version": "0.2.0-next.1", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 9bbf02ba3c..094eee92a8 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.18-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/api-report.md b/plugins/auth-backend-module-microsoft-provider/api-report.md index 71a97579d5..6aef395d7c 100644 --- a/plugins/auth-backend-module-microsoft-provider/api-report.md +++ b/plugins/auth-backend-module-microsoft-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,10 +11,10 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public @deprecated (undocumented) -export const authModuleMicrosoftProvider: BackendFeatureCompat; +export const authModuleMicrosoftProvider: BackendFeature; // @public (undocumented) -const authModuleMicrosoftProvider_2: BackendFeatureCompat; +const authModuleMicrosoftProvider_2: BackendFeature; export default authModuleMicrosoftProvider_2; // @public (undocumented) @@ -32,5 +32,9 @@ export namespace microsoftSignInResolvers { OAuthAuthenticatorResult, unknown >; + const userIdMatchingUserEntityAnnotation: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; } ``` diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index f18104ba15..1a81038994 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.1.18-next.2", + "version": "0.2.0-next.1", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts index 7ef4a57175..db7aa4f7f3 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts @@ -28,7 +28,7 @@ import { */ export namespace microsoftSignInResolvers { /** - * Looks up the user by matching their Microsoft username to the entity name. + * Looks up the user by matching their Microsoft email to the email entity annotation. */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ create() { @@ -50,4 +50,31 @@ export namespace microsoftSignInResolvers { }; }, }); + /** + * Looks up the user by matching their Microsoft user id to the user id entity annotation. + */ + export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( + { + create() { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { result } = info; + + const id = result.fullProfile.id; + + if (!id) { + throw new Error('Microsoft profile contained no id'); + } + + return ctx.signInWithCatalogUser({ + annotations: { + 'graph.microsoft.com/user-id': id, + }, + }); + }; + }, + }, + ); } diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 63c6662332..1e91fac858 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.2.4-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/api-report.md b/plugins/auth-backend-module-oauth2-provider/api-report.md index 31bfddf18e..632591ec04 100644 --- a/plugins/auth-backend-module-oauth2-provider/api-report.md +++ b/plugins/auth-backend-module-oauth2-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleOauth2Provider: BackendFeatureCompat; +const authModuleOauth2Provider: BackendFeature; export default authModuleOauth2Provider; // @public (undocumented) diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 77a00a52d6..f5ad94f1b8 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.2.4-next.2", + "version": "0.3.0-next.1", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index a9d6523201..240a3853f2 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## 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 + +## 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 + ## 0.1.16-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md b/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md index e7295bc65b..5c5cefd734 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md @@ -5,12 +5,12 @@ ```ts /// -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { IncomingHttpHeaders } from 'http'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleOauth2ProxyProvider: BackendFeatureCompat; +const authModuleOauth2ProxyProvider: BackendFeature; export default authModuleOauth2ProxyProvider; // @public diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 4df4e14dfe..9dce1fec17 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.1.16-next.2", + "version": "0.2.0-next.1", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 6c32155ebe..3b087761d0 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-backend@0.23.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.2.4-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/api-report.md b/plugins/auth-backend-module-oidc-provider/api-report.md index 0e6f807be4..cd0a99e7fc 100644 --- a/plugins/auth-backend-module-oidc-provider/api-report.md +++ b/plugins/auth-backend-module-oidc-provider/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 { BaseClient } from 'openid-client'; import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -13,7 +13,7 @@ import { TokenSet } from 'openid-client'; import { UserinfoResponse } from 'openid-client'; // @public (undocumented) -const authModuleOidcProvider: BackendFeatureCompat; +const authModuleOidcProvider: BackendFeature; export default authModuleOidcProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 5f59a4e0d1..1a1d6c9c8a 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.2.4-next.2", + "version": "0.3.0-next.1", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 38fe3bed5d..08d2ef1f2f 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.0.16-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/api-report.md b/plugins/auth-backend-module-okta-provider/api-report.md index 7676d420a8..a142265100 100644 --- a/plugins/auth-backend-module-okta-provider/api-report.md +++ b/plugins/auth-backend-module-okta-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleOktaProvider: BackendFeatureCompat; +const authModuleOktaProvider: BackendFeature; export default authModuleOktaProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index ad0c5c5ecb..0965e1548d 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.0.16-next.2", + "version": "0.1.0-next.1", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 476df484d2..796813e0a3 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + +## 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 + +## 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 + ## 0.1.4-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/api-report.md b/plugins/auth-backend-module-onelogin-provider/api-report.md index be4a88dba1..1991f00e0b 100644 --- a/plugins/auth-backend-module-onelogin-provider/api-report.md +++ b/plugins/auth-backend-module-onelogin-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -11,7 +11,7 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -const authModuleOneLoginProvider: BackendFeatureCompat; +const authModuleOneLoginProvider: BackendFeature; export default authModuleOneLoginProvider; // @public (undocumented) diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index 1d1feee009..53e8e646ca 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.1.4-next.2", + "version": "0.2.0-next.1", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 8b23915d26..ebbff3416d 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + +## 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 + +## 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 + ## 0.1.17-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/api-report.md b/plugins/auth-backend-module-pinniped-provider/api-report.md index 8d1380daff..e15f4ee324 100644 --- a/plugins/auth-backend-module-pinniped-provider/api-report.md +++ b/plugins/auth-backend-module-pinniped-provider/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 { BaseClient } from 'openid-client'; import { Config } from '@backstage/config'; import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; @@ -11,10 +11,10 @@ import { Strategy } from 'openid-client'; import { TokenSet } from 'openid-client'; // @public @deprecated (undocumented) -export const authModulePinnipedProvider: BackendFeatureCompat; +export const authModulePinnipedProvider: BackendFeature; // @public (undocumented) -const authModulePinnipedProvider_2: BackendFeatureCompat; +const authModulePinnipedProvider_2: BackendFeature; export default authModulePinnipedProvider_2; // @public (undocumented) diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index c720d9b501..932ad93b38 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.1.17-next.2", + "version": "0.2.0-next.1", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 9f81a53961..8b6540ddf3 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/catalog-model@1.6.0 + +## 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 + +## 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 + ## 0.2.4-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/api-report.md b/plugins/auth-backend-module-vmware-cloud-provider/api-report.md index 32d706f130..2a27d8fc7c 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/api-report.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; @@ -12,7 +12,7 @@ import { SignInResolverFactory } from '@backstage/plugin-auth-node'; import { Strategy } from 'passport-oauth2'; // @public -const authModuleVmwareCloudProvider: BackendFeatureCompat; +const authModuleVmwareCloudProvider: BackendFeature; export default authModuleVmwareCloudProvider; // @public diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 166b7e8e82..2f198ebacb 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.2.4-next.2", + "version": "0.3.0-next.1", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index f99c8d0453..4244638abd 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,137 @@ # @backstage/plugin-auth-backend +## 0.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.23.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.0 + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 + +## 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 + ## 0.22.10-next.2 ### Patch Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 56c2da3a08..51f80024ae 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -12,7 +12,7 @@ import { AuthResolverContext as AuthResolverContext_2 } from '@backstage/plugin- import { AuthService } from '@backstage/backend-plugin-api'; import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-module-aws-alb-provider'; import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageSignInResult } from '@backstage/plugin-auth-node'; import { CacheService } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; @@ -20,6 +20,7 @@ import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; import { Config } from '@backstage/config'; import { CookieConfigurer as CookieConfigurer_2 } from '@backstage/plugin-auth-node'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { decodeOAuthState } from '@backstage/plugin-auth-node'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { encodeOAuthState } from '@backstage/plugin-auth-node'; @@ -33,11 +34,10 @@ import { OAuth2ProxyResult as OAuth2ProxyResult_2 } from '@backstage/plugin-auth import { OAuthEnvironmentHandler as OAuthEnvironmentHandler_2 } from '@backstage/plugin-auth-node'; import { OAuthState as OAuthState_2 } from '@backstage/plugin-auth-node'; import { OidcAuthResult as OidcAuthResult_2 } from '@backstage/plugin-auth-backend-module-oidc-provider'; -import { PluginDatabaseManager } from '@backstage/backend-common'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { prepareBackstageIdentityResponse as prepareBackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; import { Profile } from 'passport'; import { ProfileInfo as ProfileInfo_2 } from '@backstage/plugin-auth-node'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { SignInInfo as SignInInfo_2 } from '@backstage/plugin-auth-node'; import { SignInResolver as SignInResolver_2 } from '@backstage/plugin-auth-node'; import { TokenManager } from '@backstage/backend-common'; @@ -57,7 +57,7 @@ export type AuthHandlerResult = { }; // @public -const authPlugin: BackendFeatureCompat; +const authPlugin: BackendFeature; export default authPlugin; // @public @deprecated (undocumented) @@ -125,7 +125,7 @@ export type BitbucketServerOAuthResult = { export class CatalogIdentityClient { constructor(options: { catalogApi: CatalogApi; - tokenManager: TokenManager; + tokenManager?: TokenManager; discovery: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; @@ -193,7 +193,7 @@ export function createAuthProviderIntegration< // @public (undocumented) export function createOriginFilter(config: Config): (origin: string) => boolean; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; // @public @@ -533,6 +533,7 @@ export const providers: Readonly<{ resolvers: Readonly<{ emailMatchingUserEntityProfileEmail: () => SignInResolver_2; emailLocalPartMatchingUserEntityName: () => SignInResolver_2; + userIdMatchingUserEntityAnnotation: () => SignInResolver_2; emailMatchingUserEntityAnnotation: () => SignInResolver_2; }>; }>; @@ -647,20 +648,20 @@ export const providers: Readonly<{ // @public @deprecated (undocumented) export const readState: typeof decodeOAuthState; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) auth?: AuthService; // (undocumented) catalogApi?: CatalogApi; // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) - database: PluginDatabaseManager; + database: DatabaseService; // (undocumented) disableDefaultProviderFactories?: boolean; // (undocumented) - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; // (undocumented) httpAuth?: HttpAuthService; // (undocumented) @@ -672,7 +673,7 @@ export interface RouterOptions { // (undocumented) tokenFactoryAlgorithm?: string; // (undocumented) - tokenManager: TokenManager; + tokenManager?: TokenManager; } // @public (undocumented) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 8dec959418..f090b86f4e 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.10-next.2", + "version": "0.23.0-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/auth-backend/src/authPlugin.ts b/plugins/auth-backend/src/authPlugin.ts index 7687462d32..89b7e9a780 100644 --- a/plugins/auth-backend/src/authPlugin.ts +++ b/plugins/auth-backend/src/authPlugin.ts @@ -65,7 +65,6 @@ export const authPlugin = createBackendPlugin({ config: coreServices.rootConfig, database: coreServices.database, discovery: coreServices.discovery, - tokenManager: coreServices.tokenManager, auth: coreServices.auth, httpAuth: coreServices.httpAuth, catalogApi: catalogServiceRef, @@ -76,7 +75,6 @@ export const authPlugin = createBackendPlugin({ config, database, discovery, - tokenManager, auth, httpAuth, catalogApi, @@ -86,7 +84,6 @@ export const authPlugin = createBackendPlugin({ config, database, discovery, - tokenManager, auth, httpAuth, catalogApi, diff --git a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts b/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts index ef3d0f6c8b..4d696aa8f2 100644 --- a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts +++ b/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; import { Knex } from 'knex'; import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index 51c72eeb63..e91108dbf2 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -45,7 +45,7 @@ export class CatalogIdentityClient { constructor(options: { catalogApi: CatalogApi; - tokenManager: TokenManager; + tokenManager?: TokenManager; discovery: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts index a69c9c90d4..7fb759200c 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts @@ -34,7 +34,6 @@ describe('CatalogAuthResolverContext', () => { logger: mockServices.logger.mock(), catalogApi: mockCatalogApi as CatalogApi, tokenIssuer: {} as TokenIssuer, - tokenManager: mockServices.tokenManager(), discovery: {} as DiscoveryService, auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts index c81ac4c355..51b374b468 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts @@ -66,7 +66,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { logger: LoggerService; catalogApi: CatalogApi; tokenIssuer: TokenIssuer; - tokenManager: TokenManager; + tokenManager?: TokenManager; discovery: DiscoveryService; auth: AuthService; httpAuth: HttpAuthService; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index f6a3a83bcb..f0b019d1c7 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -65,5 +65,7 @@ export const microsoft = createAuthProviderIntegration({ commonSignInResolvers.emailMatchingUserEntityProfileEmail(), emailMatchingUserEntityAnnotation: microsoftSignInResolvers.emailMatchingUserEntityAnnotation(), + userIdMatchingUserEntityAnnotation: + microsoftSignInResolvers.userIdMatchingUserEntityAnnotation(), }), }); diff --git a/plugins/auth-backend/src/providers/router.ts b/plugins/auth-backend/src/providers/router.ts index d84133685c..e449372143 100644 --- a/plugins/auth-backend/src/providers/router.ts +++ b/plugins/auth-backend/src/providers/router.ts @@ -50,7 +50,7 @@ export function bindProviderRouters( discovery: PluginEndpointDiscovery; auth: AuthService; httpAuth: HttpAuthService; - tokenManager: TokenManager; + tokenManager?: TokenManager; tokenIssuer: TokenIssuer; ownershipResolver?: AuthOwnershipResolver; catalogApi?: CatalogApi; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 4bb42c3abd..af34ffc310 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -19,16 +19,17 @@ import Router from 'express-promise-router'; import cookieParser from 'cookie-parser'; import { AuthService, + DatabaseService, + DiscoveryService, HttpAuthService, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; import { defaultAuthProviderFactories } from '../providers'; import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; import { - createLegacyAuthAdapters, - PluginDatabaseManager, - PluginEndpointDiscovery, TokenManager, + createLegacyAuthAdapters, } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; import { CatalogApi } from '@backstage/catalog-client'; @@ -46,16 +47,18 @@ import { readBackstageTokenExpiration } from './readBackstageTokenExpiration'; import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; -import { Config } from '@backstage/config'; import { bindProviderRouters, ProviderFactories } from '../providers/router'; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { logger: LoggerService; - database: PluginDatabaseManager; - config: Config; - discovery: PluginEndpointDiscovery; - tokenManager: TokenManager; + database: DatabaseService; + config: RootConfigService; + discovery: DiscoveryService; + tokenManager?: TokenManager; auth?: AuthService; httpAuth?: HttpAuthService; tokenFactoryAlgorithm?: string; @@ -65,7 +68,10 @@ export interface RouterOptions { ownershipResolver?: AuthOwnershipResolver; } -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export async function createRouter( options: RouterOptions, ): Promise { diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 1905329e0a..4565a93cf7 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-auth-node +## 0.5.2-next.1 + +### Patch Changes + +- c46eb0f: Extend the "unable to resolve user identity" message +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 + +## 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 + ## 0.5.0-next.2 ### Minor Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 348db40dd7..23818fcc31 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.5.0-next.2", + "version": "0.5.2-next.1", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts index 82b4918326..ed8f4baeeb 100644 --- a/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts +++ b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts @@ -66,6 +66,8 @@ export function readDeclarativeSignInResolver( } } - throw new Error('Failed to sign-in, unable to resolve user identity'); + throw new Error( + 'Failed to sign-in, unable to resolve user identity. Please verify that your catalog contains the expected User entities that would match your configured sign-in resolver.', + ); }; } diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index f7d621164c..2aac588abe 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-react +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 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 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index b68e581c74..2f76c289b3 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.5-next.0", + "version": "0.1.6-next.0", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 30e2e77a6e..3de470cfba 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index d2f22cc966..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.22-next.1", + "version": "0.2.22", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 69c16e9875..aea8617e9b 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,79 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 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 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 428a39d934..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) diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index 0a9d9c97db..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?: { @@ -69,7 +69,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -97,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 443dfebb87..1d9891ade3 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.18-next.2", + "version": "0.4.2-next.1", "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/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/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 0c58fe3f3a..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({ 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 89d6847511..4215a5a156 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,58 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 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 de18ae5895..ce62913fe5 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.43-next.2", + "version": "0.2.2-next.1", "description": "A Backstage catalog backend module that helps integrate towards Azure", "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/config": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", 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 771702d86d..c04f478566 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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 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 d9ba70c806..54de9a7b3b 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.6-next.2", + "version": "0.4.0-next.1", "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 ec27de045c..8def9570bf 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- 19ff127: Internal refactor to remove dependencies on the identity and token manager services, which have been removed. Public APIs no longer require the identity service or token manager to be provided. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 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 3f4389788c..04de284c56 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.10-next.2", + "version": "0.3.2-next.1", "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 80d09f3e2f..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,10 +16,10 @@ import { TokenManager } from '@backstage/backend-common'; import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -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); } @@ -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 46469b747a..e45ac208c4 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 eeafae91d5..ea2c30f112 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.37-next.2", + "version": "0.2.2-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", @@ -50,9 +50,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:^", 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 49b69f0fa6..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,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -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); } @@ -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 f5f805edd9..6af3647bd9 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,64 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 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 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 0673dd1805..4441a7ca8b 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.24-next.2", + "version": "0.3.0-next.1", "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 d4b173cc3f..abbb586446 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,58 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 b10e48d34b..2937b47db0 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.40-next.2", + "version": "0.2.2-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", @@ -47,9 +47,7 @@ "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:^", 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 ee3555c7f9..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,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -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); } @@ -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 ebaa50af30..5ade859371 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,64 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/config@1.2.0 + +## 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 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 e1f60aec67..37d2804e60 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.18-next.2", + "version": "0.3.0-next.1", "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 c7a032c43e..b956c2e9e6 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-catalog-backend-module-github +## 0.7.3-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.7.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 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..025d845644 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -5,6 +5,7 @@ ```ts import { AnalyzeOptions } from '@backstage/plugin-catalog-node'; import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; @@ -20,10 +21,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 +71,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitHubEntityProvider[]; // (undocumented) @@ -90,8 +91,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { options: { events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GithubEntityProvider[]; // (undocumented) @@ -129,6 +130,7 @@ export type GithubLocationAnalyzerOptions = { tokenManager?: TokenManager; auth?: AuthService; githubCredentialsProvider?: GithubCredentialsProvider; + catalog?: CatalogApi; }; // @public @@ -173,7 +175,7 @@ export interface GithubMultiOrgEntityProviderOptions { id: string; logger: LoggerService; orgs?: string[]; - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; } @@ -251,7 +253,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 dfde8e9718..16af5a72c5 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.6-next.2", + "version": "0.7.3-next.1", "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:^", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts index 7e6ac73054..b635878f92 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts @@ -44,6 +44,7 @@ export type GithubLocationAnalyzerOptions = { tokenManager?: TokenManager; auth?: AuthService; githubCredentialsProvider?: GithubCredentialsProvider; + catalog?: CatalogApi; }; /** @public */ @@ -54,7 +55,8 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { private readonly auth: AuthService; constructor(options: GithubLocationAnalyzerOptions) { - this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); + this.catalogClient = + options.catalog ?? new CatalogClient({ discoveryApi: options.discovery }); this.integrations = ScmIntegrations.fromConfig(options.config); this.githubCredentialsProvider = options.githubCredentialsProvider || 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/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/module/githubCatalogModule.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts index 809a1a5094..15d36a3011 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts @@ -21,6 +21,7 @@ import { import { catalogAnalysisExtensionPoint, catalogProcessingExtensionPoint, + catalogServiceRef, } from '@backstage/plugin-catalog-node/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { GithubEntityProvider } from '../providers/GithubEntityProvider'; @@ -37,34 +38,37 @@ export const githubCatalogModule = createBackendModule({ register(env) { env.registerInit({ deps: { - analyzers: catalogAnalysisExtensionPoint, + catalogAnalyzers: catalogAnalysisExtensionPoint, auth: coreServices.auth, - catalog: catalogProcessingExtensionPoint, + catalogProcessing: catalogProcessingExtensionPoint, config: coreServices.rootConfig, discovery: coreServices.discovery, events: eventsServiceRef, logger: coreServices.logger, scheduler: coreServices.scheduler, + catalog: catalogServiceRef, }, async init({ - catalog, + catalogProcessing, config, events, logger, scheduler, - analyzers, + catalogAnalyzers, discovery, auth, + catalog, }) { - analyzers.addScmLocationAnalyzer( + catalogAnalyzers.addScmLocationAnalyzer( new GithubLocationAnalyzer({ discovery, config, auth, + catalog, }), ); - catalog.addEntityProvider( + catalogProcessing.addEntityProvider( GithubEntityProvider.fromConfig(config, { events, logger, 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 5611260f87..a96c08f602 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + +## 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 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 68e1c8f252..563ae32df9 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.6-next.2", + "version": "0.2.0-next.1", "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 737f79de59..2022e3e511 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.4.2-next.1 + +### Patch Changes + +- 53b24d9: Internal update to use the new cache manager +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- b446954: Remove dependency on backend-common +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 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 9f82d3acbb..0e6f39cae4 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; @@ -109,7 +109,7 @@ export type GitlabProviderConfig = { allowInherited?: boolean; relations?: string[]; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; skipForkedRepos?: boolean; excludeRepos?: string[]; }; diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 9f009ee478..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 */ diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index d15deafd08..c99443a5d9 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.22-next.2", + "version": "0.4.2-next.1", "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:^", diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index b8e0cf3669..01e8018845 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,8 @@ 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 } from '@backstage/backend-defaults/cache'; /** * Extracts repositories out of an GitLab instance. @@ -40,7 +36,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; @@ -65,13 +61,13 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { private constructor(options: { integrations: ScmIntegrationRegistry; - pluginCache: PluginCacheManager; + pluginCache: CacheService; logger: LoggerService; skipReposWithoutExactFileMatch?: boolean; skipForkedRepos?: boolean; }) { this.integrations = options.integrations; - this.cache = options.pluginCache.getClient(); + this.cache = options.pluginCache; this.logger = options.logger; this.skipReposWithoutExactFileMatch = options.skipReposWithoutExactFileMatch || false; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 3e4aa54e4c..ffd33ca5d9 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'; @@ -227,7 +227,7 @@ export type GitlabProviderConfig = { relations?: string[]; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; /** * If the project is a fork, skip repository */ 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 f5e167e564..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,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -35,14 +35,14 @@ const server = setupServer(...handlers); 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(() => diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index bbd90795be..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({ 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 9706384ff0..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,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, registerMswTestHooks, @@ -36,14 +36,14 @@ const server = setupServer(...handlers); 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 5d803745d4..d242f90e68 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.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index fdf5f600e9..f1883213da 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'; @@ -53,7 +53,9 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalStringArray('excludeRepos') ?? []; const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; const restrictUsersToGroup = config.getOptionalBoolean('restrictUsersToGroup') ?? false; diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index e56e14b95f..2b01b76534 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,73 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 4b28e39: Updated the README to include documentation for the new backend support +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 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 diff --git a/plugins/catalog-backend-module-incremental-ingestion/README.md b/plugins/catalog-backend-module-incremental-ingestion/README.md index 0894d32031..3b847d4ad7 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/README.md +++ b/plugins/catalog-backend-module-incremental-ingestion/README.md @@ -42,57 +42,25 @@ The Incremental Entity Provider backend is designed for data sources that provid ## Installation 1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-incremental-ingestion` from the Backstage root directory. -2. In your catalog.ts, import `IncrementalCatalogBuilder` from `@backstage/plugin-catalog-backend-module-incremental-ingestion` and instantiate it with `await IncrementalCatalogBuilder.create(env, builder)`. You have to pass `builder` into `IncrementalCatalogBuilder.create` function because `IncrementalCatalogBuilder` will convert an `IncrementalEntityProvider` into an `EntityProvider` and call `builder.addEntityProvider`. -```ts -const builder = CatalogBuilder.create(env); -// incremental builder receives builder because it'll register -// incremental entity providers with the builder -const incrementalBuilder = await IncrementalCatalogBuilder.create(env, builder); -``` +2. Add the following code to the `packages/backend/src/index.ts` file: -3. After building the regular `CatalogBuilder`, build the incremental builder: +```diff ++ import { catalogModuleCustomIncrementalIngestionProvider } from './extensions/catalogCustomIncrementalIngestion'; -```ts -// Must be run first to ensure CatalogBuilder database migrations run before Incremental Entity Provider database migrations -const { processingEngine, router } = await builder.build(); -// Returns an optional - but highly recommended - set of administrative routes -const { incrementalAdminRouter } = await incrementBuilder.build(); -``` +const backend = createBackend(); -The final result should look something like this, ++ backend.add( ++ import( ++ '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha' ++ ), ++ ); -```ts -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { IncrementalCatalogBuilder } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; -import { Router } from 'express'; -import { Duration } from 'luxon'; -import { PluginEnvironment } from '../types'; +// We have created this in section **Adding an Incremental Entity Provider to the catalog** ++ backend.add(catalogModuleCustomIncrementalIngestionProvider); -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = CatalogBuilder.create(env); - // incremental builder receives builder because it'll register - // incremental entity providers with the builder - const incrementalBuilder = await IncrementalCatalogBuilder.create( - env, - builder, - ); - - builder.addProcessor(new ScaffolderEntitiesProcessor()); - - const { processingEngine, router } = await builder.build(); - const { incrementalAdminRouter } = await incrementalBuilder.build(); - - router.use(incrementalAdminRouter); - - await processingEngine.start(); - - return router; -} +backend.start(); ``` ## Administrative Routes @@ -116,7 +84,7 @@ In all cases, `:provider` is the name of the incremental entity provider. ## Writing an Incremental Entity Provider -To create an Incremental Entity Provider, you need to know how to retrieve a single page of the data that you wish to ingest into the Backstage catalog. If the API has pagination and you know how to make a paginated request to that API, you'll be able to implement an Incremental Entity Provider for this API. For more information about compatibility, check out the requirements section of this page. +To create an Incremental Entity Provider, you need to know how to retrieve a single page of the data that you wish to ingest into the Backstage catalog. If the API has pagination and you know how to make a paginated request to that API, you'll be able to implement an Incremental Entity Provider for this API. For more information about compatibility, check out the [requirements](#requirements) section of this page. Here is the type definition for an Incremental Entity Provider. @@ -309,45 +277,73 @@ Now that you have your new Incremental Entity Provider, we can connect it to the ## Adding an Incremental Entity Provider to the catalog -We'll assume you followed the Installation instructions. After you create your `incrementalBuilder`, you can instantiate your Entity Provider and pass it to the `addIncrementalEntityProvider` method. +We'll assume you followed the [Installation](#installation) instructions. Now create a module inside `packages/backend/src/extensions/catalogCustomIncrementalIngestion.ts`. ```ts -const incrementalBuilder = await IncrementalCatalogBuilder.create(env, builder); +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { incrementalIngestionProvidersExtensionPoint } from '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha'; -// Assuming the token for the API comes from config -const token = config.getString('myApiClient.token'); +export const catalogModuleCustomIncrementalIngestionProvider = + createBackendModule({ + pluginId: 'catalog', + moduleId: 'custom-incremental-ingestion-provider', + register(env) { + env.registerInit({ + deps: { + incrementalBuilder: incrementalIngestionProvidersExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ incrementalBuilder, config }) { + // Assuming the token for the API comes from config + const token = config.getString('myApiClient.token'); + const myEntityProvider = new MyIncrementalEntityProvider(token); -const myEntityProvider = new MyIncrementalEntityProvider(token); + const options = { + // How long should it attempt to read pages from the API in a + // single burst? Keep this short. The Incremental Entity Provider + // will attempt to read as many pages as it can in this time + burstLength: { seconds: 3 }, -incrementalBuilder.addIncrementalEntityProvider(myEntityProvider, { - // How long should it attempt to read pages from the API in a - // single burst? Keep this short. The Incremental Entity Provider - // will attempt to read as many pages as it can in this time - burstLength: { seconds: 3 }, + // How long should it wait between bursts? + burstInterval: { seconds: 3 }, - // How long should it wait between bursts? - burstInterval: { seconds: 3 }, + // How long should it rest before re-ingesting again? + restLength: { day: 1 }, - // How long should it rest before re-ingesting again? - restLength: { day: 1 }, + // Optional back-off configuration - how long should it wait to retry + // in the event of an error? + backoff: [ + { seconds: 5 }, + { seconds: 30 }, + { minutes: 10 }, + { hours: 3 }, + ], - // Optional back-off configuration - how long should it wait to retry - // in the event of an error? - backoff: [{ seconds: 5 }, { seconds: 30 }, { minutes: 10 }, { hours: 3 }], + // Optional. Use this to prevent removal of entities above a given + // percentage. This can be helpful if a data source is flaky and + // sometimes returns a successful status, but fewer than expected + // assets to add or maintain in the catalog. + rejectRemovalsAbovePercentage: 5, - // Optional. Use this to prevent removal of entities above a given - // percentage. This can be helpful if a data source is flaky and - // sometimes returns a successful status, but fewer than expected - // assets to add or maintain in the catalog. - rejectRemovalsAbovePercentage: 5, + // Optional. Similar to rejectRemovalsAbovePercentage, except it + // applies to complete, 100% failure of a data source. If true, + // a data source that returns a successful status but does not + // provide any assets to turn into entities will have its empty + // data set rejected. + rejectEmptySourceCollections: true, + }; - // Optional. Similar to rejectRemovalsAbovePercentage, except it - // applies to complete, 100% failure of a data source. If true, - // a data source that returns a successful status but does not - // provide any assets to turn into entities will have its empty - // data set rejected. - rejectEmptySourceCollections: true, -}); + incrementalBuilder.addProvider({ + provider: myEntityProvider, + options, + }); + }, + }); + }, + }); ``` That's it!!! 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 f3744d9b07..fe7370ac8f 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.28-next.2", + "version": "0.5.3-next.1", "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 8b8907bd7b..f48ab34ba4 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @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-node@1.12.7-next.1 + +## 0.9.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @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.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 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 fa627cb55f..c3e0f39e9f 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.7.1-next.2", + "version": "0.9.0-next.1", "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 4637f19052..05d129c042 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## 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 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 a6609c7227..0d4bbf6e5d 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.2-next.2", + "version": "0.1.0-next.1", "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 2efe171ec1..9cc7e6207b 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.6.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 3c2d690: Allow users without defined email to be ingested by the `msgraph` catalog plugin and add `userIdMatchingUserEntityAnnotation` sign-in resolver for the Microsoft auth provider to support sign-in for users without defined email. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 diff --git a/plugins/catalog-backend-module-msgraph/api-report-alpha.md b/plugins/catalog-backend-module-msgraph/api-report-alpha.md index 84f56a99bf..8129aec50f 100644 --- a/plugins/catalog-backend-module-msgraph/api-report-alpha.md +++ b/plugins/catalog-backend-module-msgraph/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 { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; import { OrganizationTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; @@ -11,7 +11,7 @@ import { ProviderConfigTransformer } from '@backstage/plugin-catalog-backend-mod import { UserTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; // @alpha -const catalogModuleMicrosoftGraphOrgEntityProvider: BackendFeatureCompat; +const catalogModuleMicrosoftGraphOrgEntityProvider: BackendFeature; export default catalogModuleMicrosoftGraphOrgEntityProvider; // @alpha diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 57f50fbd18..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'; @@ -147,7 +147,7 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { logger: LoggerService; organizationTransformer?: OrganizationTransformer; providerConfigTransformer?: ProviderConfigTransformer; - schedule: 'manual' | TaskRunner; + schedule: 'manual' | SchedulerServiceTaskRunner; target: string; userTransformer?: UserTransformer; } @@ -157,8 +157,8 @@ export type MicrosoftGraphOrgEntityProviderOptions = | MicrosoftGraphOrgEntityProviderLegacyOptions | { logger: LoggerService; - schedule?: 'manual' | TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: 'manual' | SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer | Record; groupTransformer?: GroupTransformer | Record; organizationTransformer?: @@ -218,7 +218,7 @@ export type MicrosoftGraphProviderConfig = { groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; loadUserPhotos?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index a470c075f0..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?: { @@ -217,7 +217,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -304,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 daba6c4b9f..cc8fc5abc8 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.31-next.2", + "version": "0.6.2-next.1", "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:^", @@ -68,7 +66,6 @@ "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/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 257a7d5736..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'; @@ -141,7 +141,7 @@ export type MicrosoftGraphProviderConfig = { /** * Schedule configuration for refresh tasks. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; /** @@ -338,7 +338,9 @@ export function readProviderConfig( } const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts index f123b75995..62aa758fed 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.test.ts @@ -90,4 +90,31 @@ describe('defaultTransformers', () => { }, }); }); + + it('tests defaultUserTransformer with no email', async () => { + const user: MicrosoftGraph.User = { + id: 'foo', + displayName: 'BAR', + userPrincipalName: 'test@upn', + }; + const userPhoto = 'test_photo'; + const result = await defaultUserTransformer(user, userPhoto); + expect(result).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'graph.microsoft.com/user-id': 'foo', + }, + name: 'test_upn', + }, + spec: { + memberOf: [], + profile: { + displayName: 'BAR', + picture: 'test_photo', + }, + }, + }); + }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts index 7a8c4ec463..910d5a1d40 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/defaultTransformers.ts @@ -122,25 +122,25 @@ export async function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, ): Promise { - if (!user.id || !user.displayName || !user.mail) { + if (!user.id || !user.displayName) { return undefined; } - const name = normalizeEntityName(user.mail); + const name = user.mail + ? normalizeEntityName(user.mail) + : normalizeEntityName(user.userPrincipalName!); const entity: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', metadata: { name, annotations: { - [MICROSOFT_EMAIL_ANNOTATION]: user.mail!, [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: user.id!, }, }, spec: { profile: { displayName: user.displayName!, - email: user.mail!, // TODO: Additional fields? // jobTitle: user.jobTitle || undefined, @@ -151,6 +151,11 @@ export async function defaultUserTransformer( }, }; + if (user.mail) { + entity.metadata.annotations![MICROSOFT_EMAIL_ANNOTATION] = user.mail; + entity.spec.profile!.email = user.mail; + } + if (userPhoto) { entity.spec.profile!.picture = userPhoto; } 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/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 a019ac79a3..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, @@ -41,7 +40,11 @@ import { 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}. @@ -65,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. @@ -141,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. @@ -360,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 eb0dbcf08d..b1e1c3308c 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 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 aa4ec5a803..f93f1e8119 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.41-next.2", + "version": "0.2.0-next.1", "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 209160b4e1..cfcf1acec3 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 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 977379fefa..e9f322e4c1 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.29-next.2", + "version": "0.2.2-next.1", "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:^", 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-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 0813e4b6ab..d6e8c54549 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 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 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 02c8a09ae9..3855e64192 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.21-next.2", + "version": "0.2.0-next.1", "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 37413e5f02..401f93077d 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 eaf86d75ed..78a9c345ce 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.10-next.2", + "version": "0.5.0-next.1", "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 fe707b2a9a..d2987d1182 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,109 @@ # @backstage/plugin-catalog-backend +## 1.25.3-next.1 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + +## 1.25.3-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- 53cce86: Fixed an issue with the by-query call, where ordering by a field that does not exist on all entities led to not all results being returned +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 0a046b43e4..e3a09b65ec 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.24.1-next.2", + "version": "1.25.3-next.1", "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:^", 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 7d1578db00..7e895c5344 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader, UrlReaders } from '@backstage/backend-common'; import { mockServices, registerMswTestHooks, @@ -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'; @@ -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/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/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 3c98f2a8bc..92acd9e3ce 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -1648,6 +1648,43 @@ describe('DefaultEntitiesCatalog', () => { expect(response5.totalItems).toBe(6); }, ); + + it.each(databases.eachSupportedId())( + 'should sort properly for fields that do not exist on all entities, %p', + async databaseId => { + await createDatabase(databaseId); + + await Promise.all([ + addEntityToSearch(entityFrom('AA', { uid: 'id1' })), + addEntityToSearch(entityFrom('BB', { uid: 'id2', title: 'YY' })), + addEntityToSearch(entityFrom('CC', { uid: 'id3', title: 'XX' })), + ]); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: mockServices.logger.mock(), + stitcher, + }); + + await expect( + catalog + .queryEntities({ + orderFields: [{ field: 'metadata.title', order: 'asc' }], + credentials: mockCredentials.none(), + }) + .then(r => r.items.map(e => e.metadata.name)), + ).resolves.toEqual(['CC', 'BB', 'AA']); // 'AA' has no title, ends up last + + await expect( + catalog + .queryEntities({ + orderFields: [{ field: 'metadata.title', order: 'desc' }], + credentials: mockCredentials.none(), + }) + .then(r => r.items.map(e => e.metadata.name)), + ).resolves.toEqual(['BB', 'CC', 'AA']); // 'AA' has no title, ends up last + }, + ); }); describe('removeEntityByUid', () => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 9049685d38..f9f731e3e4 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -249,7 +249,18 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { ]); } }); - entitiesQuery = entitiesQuery.orderBy('final_entities.entity_id', 'asc'); // stable sort + + if (!request?.order) { + entitiesQuery = entitiesQuery + .leftOuterJoin( + 'refresh_state', + 'refresh_state.entity_id', + 'final_entities.entity_id', + ) + .orderBy('refresh_state.entity_ref', 'asc'); // default sort + } else { + entitiesQuery.orderBy('final_entities.entity_id', 'asc'); // stable sort + } const { limit, offset } = parsePagination(request?.pagination); if (limit !== undefined) { @@ -379,12 +390,20 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { const [prevItemOrderFieldValue, prevItemUid] = cursor.orderFieldValues || []; - const dbQuery = db('search') - .join('final_entities', 'search.entity_id', 'final_entities.entity_id') - .where('search.key', sortField.field); + const dbQuery = db('final_entities').leftOuterJoin('search', qb => + qb + .on('search.entity_id', 'final_entities.entity_id') + .andOnVal('search.key', sortField.field), + ); if (cursor.filter) { - parseFilter(cursor.filter, dbQuery, db, false, 'search.entity_id'); + parseFilter( + cursor.filter, + dbQuery, + db, + false, + 'final_entities.entity_id', + ); } const normalizedFullTextFilterTerm = cursor.fullTextFilter?.term?.trim(); @@ -410,7 +429,7 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { `%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`, ); }); - dbQuery.andWhere('search.entity_id', 'in', matchQuery); + dbQuery.andWhere('final_entities.entity_id', 'in', matchQuery); } } @@ -427,32 +446,59 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { ) .orWhere('value', '=', prevItemOrderFieldValue) .andWhere( - 'search.entity_id', + 'final_entities.entity_id', isFetchingBackwards !== isOrderingDescending ? '<' : '>', prevItemUid, ); }); } - dbQuery - .orderBy([ + if (db.client.config.client === 'pg') { + // pg correctly orders by the column value and handling nulls in one go + dbQuery.orderBy([ { - column: 'value', + column: 'search.value', + order: isFetchingBackwards + ? invertOrder(sortField.order) + : sortField.order, + nulls: 'last', + }, + { + column: 'final_entities.entity_id', + order: isFetchingBackwards + ? invertOrder(sortField.order) + : sortField.order, + }, + ]); + } else { + // sqlite and mysql translate the above statement ONLY into "order by (value is null) asc" + // no matter what the order is, for some reason, so we have to manually add back the statement + // that translates to "order by value " while avoiding to give an order + dbQuery.orderBy([ + { + column: 'search.value', + order: undefined, + nulls: 'last', + }, + { + column: 'search.value', order: isFetchingBackwards ? invertOrder(sortField.order) : sortField.order, }, { - column: 'search.entity_id', + column: 'final_entities.entity_id', order: isFetchingBackwards ? invertOrder(sortField.order) : sortField.order, }, - ]) - // fetch an extra item to check if there are more items. - .limit(isFetchingBackwards ? limit : limit + 1); + ]); + } - countQuery.count('search.entity_id', { as: 'count' }); + // fetch an extra item to check if there are more items. + dbQuery.limit(isFetchingBackwards ? limit : limit + 1); + + countQuery.count('final_entities.entity_id', { as: 'count' }); const [rows, [{ count }]] = await Promise.all([ limit > 0 ? dbQuery : [], 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 2d0fde55da..f15f53aef4 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index cfcfee74d8..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.26-next.1", + "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 695af8d4fe..dd6cfdaf24 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-catalog-graph +## 0.4.9-next.1 + +### Patch Changes + +- da91078: Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 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 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 e651946463..65f8af3764 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -3,12 +3,20 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } 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 { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { catalogGraph: RouteRef; }, @@ -19,7 +27,134 @@ const _default: BackstagePlugin< namespace: string; }>; }, - {} + { + 'entity-card:catalog-graph/relations': ExtensionDefinition<{ + config: { + 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; + }; + configInput: { + 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; + }; + output: + | 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; + } + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'entity-card'; + namespace: undefined; + name: 'relations'; + }>; + 'page:catalog-graph': ExtensionDefinition<{ + config: { + 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; + }; + configInput: { + 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; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'page'; + namespace: undefined; + name: undefined; + }>; + } >; export default _default; diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 08a5497dc0..74b9054492 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.8-next.3", + "version": "0.4.9-next.1", "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/CatalogGraphPage/useCatalogGraphPage.test.ts b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.ts deleted file mode 100644 index a89fa353d6..0000000000 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { RELATION_MEMBER_OF } from '@backstage/catalog-model'; -import { act, renderHook } from '@testing-library/react'; -import { useLocation as useLocationMocked } from 'react-router-dom'; -import { Direction } from '../EntityRelationsGraph'; -import { useCatalogGraphPage } from './useCatalogGraphPage'; - -jest.mock('react-router-dom', () => ({ - useLocation: jest.fn(), -})); - -jest.spyOn(window.history, 'replaceState'); -jest.spyOn(window.history, 'pushState'); - -const useLocation = useLocationMocked as jest.Mock< - ReturnType ->; -const windowHistoryReplaceState = window.history.replaceState as jest.Mock< - ReturnType ->; -const windowHistoryPushState = window.history.pushState as jest.Mock< - ReturnType ->; - -describe('useCatalogGraphPage', () => { - beforeEach(() => { - useLocation.mockReturnValue({ - search: '?', - state: {}, - key: '', - pathname: '', - hash: '', - }); - }); - - afterEach(() => jest.resetAllMocks()); - - test('should use initial state', () => { - const { result } = renderHook(() => - useCatalogGraphPage({ - initialState: { - rootEntityRefs: ['b:d/c'], - maxDepth: 2, - direction: Direction.RIGHT_LEFT, - mergeRelations: false, - unidirectional: false, - showFilters: false, - selectedKinds: ['API'], - selectedRelations: [RELATION_MEMBER_OF], - }, - }), - ); - - expect(result.current.rootEntityNames).toEqual([ - { kind: 'b', namespace: 'd', name: 'c' }, - ]); - expect(result.current.maxDepth).toEqual(2); - expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); - expect(result.current.mergeRelations).toEqual(false); - expect(result.current.unidirectional).toEqual(false); - expect(result.current.showFilters).toEqual(false); - expect(result.current.selectedKinds).toEqual(['api']); - expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); - }); - - test('should use state from url', () => { - useLocation.mockReturnValueOnce({ - search: - '?rootEntityRefs[]=b:d/c&maxDepth=2&direction=RL&mergeRelations=false&unidirectional=false&showFilters=false&selectedKinds[]=api&selectedRelations[]=memberOf', - state: {}, - key: '', - pathname: '', - hash: '', - }); - - const { result } = renderHook(() => useCatalogGraphPage({})); - - expect(result.current.rootEntityNames).toEqual([ - { kind: 'b', namespace: 'd', name: 'c' }, - ]); - expect(result.current.maxDepth).toEqual(2); - expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); - expect(result.current.mergeRelations).toEqual(false); - expect(result.current.unidirectional).toEqual(false); - expect(result.current.showFilters).toEqual(false); - expect(result.current.selectedKinds).toEqual(['api']); - expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); - }); - - test('should update state in url (replace if setting changes)', () => { - const { result } = renderHook(() => useCatalogGraphPage({})); - - act(() => result.current.setMaxDepth(5)); - - expect(windowHistoryReplaceState).toHaveBeenCalledWith( - null, - '', - '/?maxDepth=5&unidirectional=true&mergeRelations=true&direction=LR&showFilters=true', - ); - - act(() => result.current.setUnidirectional(false)); - - expect(windowHistoryReplaceState).toHaveBeenCalledWith( - null, - '', - '/?maxDepth=5&unidirectional=false&mergeRelations=true&direction=LR&showFilters=true', - ); - }); - - test('should update state in url (only push if different root entity)', () => { - const { result, rerender } = renderHook(() => - useCatalogGraphPage({ - initialState: { - rootEntityRefs: ['component:default/first'], - }, - }), - ); - - act(() => - result.current.setRootEntityNames([ - { kind: 'Component', namespace: 'default', name: 'my' }, - ]), - ); - - rerender(); - - expect(windowHistoryPushState).toHaveBeenCalledWith( - null, - '', - '/?rootEntityRefs%5B%5D=component%3Adefault%2Fmy&maxDepth=%E2%88%9E&unidirectional=true&mergeRelations=true&direction=LR&showFilters=true', - ); - }); -}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx new file mode 100644 index 0000000000..85cb606482 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx @@ -0,0 +1,182 @@ +/* + * 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 { RELATION_MEMBER_OF } from '@backstage/catalog-model'; +import { renderHook, waitFor } from '@testing-library/react'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BrowserRouter } from 'react-router-dom'; +import { Direction } from '../EntityRelationsGraph'; +import { useCatalogGraphPage } from './useCatalogGraphPage'; + +const wrapper = ({ children }: { children?: React.ReactNode }) => { + return {children}; +}; + +describe('useCatalogGraphPage', () => { + test('should use initial state', () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: { + initialState: { + rootEntityRefs: ['b:d/c'], + maxDepth: 2, + direction: Direction.RIGHT_LEFT, + mergeRelations: false, + unidirectional: false, + showFilters: false, + selectedKinds: ['API'], + selectedRelations: [RELATION_MEMBER_OF], + }, + }, + wrapper, + }); + + expect(result.current.rootEntityNames).toEqual([ + { kind: 'b', namespace: 'd', name: 'c' }, + ]); + expect(result.current.maxDepth).toEqual(2); + expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); + expect(result.current.mergeRelations).toEqual(false); + expect(result.current.unidirectional).toEqual(false); + expect(result.current.showFilters).toEqual(false); + expect(result.current.selectedKinds).toEqual(['api']); + expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); + }); + + test('should use state from url', () => { + act(() => { + history.pushState( + {}, + '', + '?rootEntityRefs[]=b:d/c&maxDepth=2&direction=RL&mergeRelations=false&unidirectional=false&showFilters=false&selectedKinds[]=api&selectedRelations[]=memberOf', + ); + }); + + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: {}, + wrapper, + }); + + expect(result.current.rootEntityNames).toEqual([ + { kind: 'b', namespace: 'd', name: 'c' }, + ]); + expect(result.current.maxDepth).toEqual(2); + expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); + expect(result.current.mergeRelations).toEqual(false); + expect(result.current.unidirectional).toEqual(false); + expect(result.current.showFilters).toEqual(false); + expect(result.current.selectedKinds).toEqual(['api']); + expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); + }); + + test('should update state in url (replace if setting changes)', () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + wrapper, + initialProps: {}, + }); + + act(() => result.current.setMaxDepth(5)); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=b%3Ad%2Fc&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => result.current.setUnidirectional(false)); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=b%3Ad%2Fc&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + }); + + test('should update state in url (only push if different root entity)', () => { + const oldLength = window.history.length; + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: { + initialState: { + rootEntityRefs: ['component:default/first'], + }, + }, + wrapper, + }); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'my' }, + ]), + ); + + expect(window.history.length).toEqual(oldLength + 1); + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Fmy&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + }); + + test('should update state to last state on back', async () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + wrapper, + initialProps: { + initialState: { + rootEntityRefs: ['component:default/first'], + }, + }, + }); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'first' }, + ]), + ); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Ffirst&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'second' }, + ]), + ); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Fsecond&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => { + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'third' }, + ]); + }); + + act(() => { + window.history.back(); + }); + + await waitFor(() => { + expect(result.current.rootEntityNames).toEqual([ + { kind: 'component', namespace: 'default', name: 'second' }, + ]); + }); + + act(() => { + window.history.back(); + }); + + await waitFor(() => { + expect(result.current.rootEntityNames).toEqual([ + { kind: 'component', namespace: 'default', name: 'first' }, + ]); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts index 6e47dedc50..1f6fb74997 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts @@ -27,13 +27,12 @@ import { useMemo, useState, } from 'react'; -import { useLocation } from 'react-router-dom'; -import usePrevious from 'react-use/esm/usePrevious'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Direction } from '../EntityRelationsGraph'; export type CatalogGraphPageValue = { rootEntityNames: CompoundEntityRef[]; - setRootEntityNames: Dispatch>; + setRootEntityNames: (value: CompoundEntityRef[]) => void; maxDepth: number; setMaxDepth: Dispatch>; selectedRelations: string[] | undefined; @@ -70,6 +69,8 @@ export function useCatalogGraphPage({ }; }): CatalogGraphPageValue { const location = useLocation(); + const navigate = useNavigate(); + const query = useMemo( () => (qs.parse(location.search, { arrayLimit: 0, ignoreQueryPrefix: true }) || @@ -87,19 +88,46 @@ export function useCatalogGraphPage({ [location.search], ); - // Initial state - const [rootEntityNames, setRootEntityNames] = useState( + const rootEntityNames = useMemo( () => (Array.isArray(query.rootEntityRefs) ? query.rootEntityRefs : initialState?.rootEntityRefs ?? [] ).map(r => parseEntityRef(r)), + [initialState?.rootEntityRefs, query.rootEntityRefs], ); + + const setRootEntityNames = useCallback( + (value: CompoundEntityRef[]) => { + const areSame = + rootEntityNames.length === value.length && + rootEntityNames.every( + (r, i) => stringifyEntityRef(r) === stringifyEntityRef(value[i]), + ); + + if (areSame) { + return; + } + + const newSearch = qs.stringify( + { + ...query, + rootEntityRefs: value.map(r => stringifyEntityRef(r)), + }, + { arrayFormat: 'brackets', addQueryPrefix: true }, + ); + + navigate(newSearch); + }, + [rootEntityNames, navigate, query], + ); + const [maxDepth, setMaxDepth] = useState(() => typeof query.maxDepth === 'string' ? parseMaxDepth(query.maxDepth) : initialState?.maxDepth ?? Number.POSITIVE_INFINITY, ); + const [selectedRelations, setSelectedRelations] = useState< string[] | undefined >(() => @@ -107,105 +135,53 @@ export function useCatalogGraphPage({ ? query.selectedRelations : initialState?.selectedRelations, ); + const [selectedKinds, setSelectedKinds] = useState(() => (Array.isArray(query.selectedKinds) ? query.selectedKinds : initialState?.selectedKinds )?.map(k => k.toLocaleLowerCase('en-US')), ); + const [unidirectional, setUnidirectional] = useState(() => typeof query.unidirectional === 'string' ? query.unidirectional === 'true' : initialState?.unidirectional ?? true, ); + const [mergeRelations, setMergeRelations] = useState(() => typeof query.mergeRelations === 'string' ? query.mergeRelations === 'true' : initialState?.mergeRelations ?? true, ); + const [direction, setDirection] = useState(() => typeof query.direction === 'string' ? query.direction : initialState?.direction ?? Direction.LEFT_RIGHT, ); + const [curve, setCurve] = useState<'curveStepBefore' | 'curveMonotoneX'>(() => typeof query.curve === 'string' ? query.curve : initialState?.curve ?? 'curveMonotoneX', ); + const [showFilters, setShowFilters] = useState(() => typeof query.showFilters === 'string' ? query.showFilters === 'true' : initialState?.showFilters ?? true, ); + const toggleShowFilters = useCallback( () => setShowFilters(s => !s), [setShowFilters], ); - // Update from query parameters - const prevQueryParams = usePrevious(location.search); useEffect(() => { - // Only respond to changes to url query params - if (location.search === prevQueryParams) { - return; - } - - if (Array.isArray(query.rootEntityRefs)) { - setRootEntityNames(query.rootEntityRefs.map(r => parseEntityRef(r))); - } - - if (typeof query.maxDepth === 'string') { - setMaxDepth(parseMaxDepth(query.maxDepth)); - } - - if (Array.isArray(query.selectedKinds)) { - setSelectedKinds(query.selectedKinds); - } - - if (Array.isArray(query.selectedRelations)) { - setSelectedRelations(query.selectedRelations); - } - - if (typeof query.unidirectional === 'string') { - setUnidirectional(query.unidirectional === 'true'); - } - - if (typeof query.mergeRelations === 'string') { - setMergeRelations(query.mergeRelations === 'true'); - } - - if (typeof query.direction === 'string') { - setDirection(query.direction); - } - - if (typeof query.showFilters === 'string') { - setShowFilters(query.showFilters === 'true'); - } - }, [ - prevQueryParams, - location.search, - query, - setRootEntityNames, - setMaxDepth, - setSelectedKinds, - setSelectedRelations, - setUnidirectional, - setMergeRelations, - setDirection, - setShowFilters, - ]); - - // Update query parameters - const previousRootEntityRefs = usePrevious( - rootEntityNames.map(e => stringifyEntityRef(e)), - ); - - useEffect(() => { - const rootEntityRefs = rootEntityNames.map(e => stringifyEntityRef(e)); const newParams = qs.stringify( { - rootEntityRefs, + rootEntityRefs: rootEntityNames.map(stringifyEntityRef), maxDepth: isFinite(maxDepth) ? maxDepth : '∞', selectedKinds, selectedRelations, @@ -213,36 +189,23 @@ export function useCatalogGraphPage({ mergeRelations, direction, showFilters, + curve, }, { arrayFormat: 'brackets', addQueryPrefix: true }, ); - const newUrl = `${window.location.pathname}${newParams}`; - // We directly manipulate window history here in order to not re-render - // infinitely (state => location => state => etc). The intention of this - // code is just to ensure the right query/filters are loaded when a user - // clicks the "back" button after clicking a result. - // Only push a new history entry if we switched to another entity, but not - // if we just changed a viewer setting. - if ( - !previousRootEntityRefs || - (rootEntityRefs.length === previousRootEntityRefs.length && - rootEntityRefs.every((v, i) => v === previousRootEntityRefs[i])) - ) { - window.history.replaceState(null, document.title, newUrl); - } else { - window.history.pushState(null, document.title, newUrl); - } + navigate(newParams, { replace: true }); }, [ - rootEntityNames, maxDepth, + curve, selectedKinds, selectedRelations, unidirectional, mergeRelations, direction, showFilters, - previousRootEntityRefs, + rootEntityNames, + navigate, ]); return { diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 2063f9e2f9..0456c5ee24 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,79 @@ # @backstage/plugin-catalog-import +## 0.12.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.12.3-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 diff --git a/plugins/catalog-import/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 51e3d99fd7..3eed9ce3e4 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -3,16 +3,61 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } 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: FrontendPlugin< { importPage: RouteRef; }, {}, - {} + { + 'api:catalog-import': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'page:catalog-import': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 44076a3fbc..32e183e7d9 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.2-next.2", + "version": "0.12.3-next.1", "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-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 24f1446c6d..b4472333fd 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-catalog-node +## 1.12.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 1.12.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## 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 diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 330427c7b5..25f8d699ab 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.5-next.2", + "version": "1.12.7-next.1", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 24a1cf6572..5027678cbe 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/plugin-catalog-react +## 1.12.4-next.1 + +### Patch Changes + +- ae9b6cb: Small internal fix to better work with recent `lodash` versions +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @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-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## 1.12.4-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 5446061: The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## 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 diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index edfa183903..5dec3ab99d 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -5,35 +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< - string, - 'catalog.entity-content-title', - {} - >; - entityFilterFunction: ConfigurableExtensionDataRef< - (entity: Entity) => boolean, - 'catalog.entity-filter-function', - {} - >; - entityFilterExpression: ConfigurableExtensionDataRef< - string, - 'catalog.entity-filter-expression', - {} - >; -}; - // @alpha (undocumented) export const catalogReactTranslationRef: TranslationRef< 'catalog-react', @@ -93,76 +75,141 @@ 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< - TConfig, - TConfig, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; +): 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; + params: { + loader: () => Promise; + filter?: string | ((entity: Entity) => boolean) | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + dataRefs: { + 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; + params: { + loader: () => Promise; + defaultPath: string; + defaultTitle: string; + routeRef?: RouteRef | undefined; + filter?: string | ((entity: Entity) => boolean) | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + config: { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }; + configInput: { filter?: string | undefined; title?: string | undefined; path?: string | undefined; - }, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; + }; + dataRefs: { + 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 7300729db5..ba4aff5c42 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.3-next.2", + "version": "1.12.4-next.1", "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 42d69726b6..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().with({ - id: 'catalog.entity-content-title', - }), - entityFilterFunction: createExtensionDataRef< - (entity: Entity) => boolean - >().with({ id: 'catalog.entity-filter-function' }), - entityFilterExpression: createExtensionDataRef().with({ - id: 'catalog.entity-filter-expression', - }), -}; - -// TODO: Figure out how to merge with provided config schema -/** @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, - 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, - 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..2ab87b20ae --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -0,0 +1,184 @@ +/* + * 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, + renderInTestApp, +} 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", + "T": undefined, + "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
    )]; + }, + }); + + renderInTestApp( + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .reactElement(), + ); + + 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..cc86f01501 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -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 React from 'react'; +import { EntityContentBlueprint } from './EntityContentBlueprint'; +import { + createExtensionTester, + renderInTestApp, +} 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", + "T": undefined, + "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
    )]; + }, + }); + + renderInTestApp( + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .reactElement(), + ); + + 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/backend-tasks/src/tasks/__testUtils__/createTestScopedSignal.ts b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx similarity index 51% rename from packages/backend-tasks/src/tasks/__testUtils__/createTestScopedSignal.ts rename to plugins/catalog-react/src/alpha/blueprints/extensionData.tsx index 6a497ea266..09aac809cb 100644 --- a/packages/backend-tasks/src/tasks/__testUtils__/createTestScopedSignal.ts +++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx @@ -14,15 +14,21 @@ * limitations under the License. */ -export function createTestScopedSignal(): () => AbortSignal { - let testAbortController = new AbortController(); +import { Entity } from '@backstage/catalog-model'; +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; - beforeEach(() => { - testAbortController = new AbortController(); - }); - afterEach(() => { - testAbortController.abort(); - }); +/** @internal */ +export const entityContentTitleDataRef = createExtensionDataRef().with({ + id: 'catalog.entity-content-title', +}); - return () => testAbortController.signal; -} +/** @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', + }); 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..dd13318333 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx @@ -0,0 +1,127 @@ +/* + * 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); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('entity-card:example'); + + 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', + }); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('entity-card:other'); + + 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 getDiscoveredId = (name: string) => { + const converted = convertLegacyEntityCardExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ), + ); + return createExtensionTester(converted).query(converted).node.spec.id; + }; + + expect(getDiscoveredId('EntityTestCard')).toBe('entity-card:test'); // falls back to test namespace + expect(getDiscoveredId('EntityTestTrimCard')).toBe('entity-card:trim'); + expect(getDiscoveredId('EntityTeStTrimCard')).toBe('entity-card:trim'); + expect(getDiscoveredId('EntityExampleCard')).toBe('entity-card:example'); + expect(getDiscoveredId('EntityExAmpleCard')).toBe('entity-card:ex-ample'); + expect(getDiscoveredId('ExampleCard')).toBe('entity-card: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..ed92461736 --- /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..d8ca126aa2 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx @@ -0,0 +1,134 @@ +/* + * 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); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('entity-content:example'); + + 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', + }); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('entity-content:other'); + + 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 createExtensionTester(converted).query(converted).node.spec.id; + }; + + expect(withName('EntityTestContent')).toBe('entity-content:test'); // falls back to test namespace + expect(withName('EntityTestTrimContent')).toBe('entity-content:trim'); + expect(withName('EntityTeStTrimContent')).toBe('entity-content:trim'); + expect(withName('EntityExampleContent')).toBe('entity-content:example'); + expect(withName('EntityExAmpleContent')).toBe('entity-content:ex-ample'); + expect(withName('ExampleContent')).toBe('entity-content: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..02f5d104bc --- /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/apis/EntityPresentationApi/defaultEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts index 8d841f3c3c..b546f9aa93 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts @@ -71,6 +71,9 @@ export function defaultEntityPresentation( }; } +const isString = (value: unknown): value is string => + Boolean(value) && typeof value === 'string'; + // Try to extract display-worthy parts of an entity or ref as best we can, without throwing function getParts(entityOrRef: Entity | CompoundEntityRef | string): { kind?: string; @@ -99,35 +102,29 @@ function getParts(entityOrRef: Entity | CompoundEntityRef | string): { } if (typeof entityOrRef === 'object' && entityOrRef !== null) { - const kind = [get(entityOrRef, 'kind')].find( - candidate => candidate && typeof candidate === 'string', - ); + const kind = [get(entityOrRef, 'kind')].find(isString); const namespace = [ get(entityOrRef, 'metadata.namespace'), get(entityOrRef, 'namespace'), - ].find(candidate => candidate && typeof candidate === 'string'); + ].find(isString); const name = [ get(entityOrRef, 'metadata.name'), get(entityOrRef, 'name'), - ].find(candidate => candidate && typeof candidate === 'string'); + ].find(isString); - const title = [get(entityOrRef, 'metadata.title')].find( - candidate => candidate && typeof candidate === 'string', - ); + const title = [get(entityOrRef, 'metadata.title')].find(isString); const description = [get(entityOrRef, 'metadata.description')].find( - candidate => candidate && typeof candidate === 'string', + isString, ); const displayName = [get(entityOrRef, 'spec.profile.displayName')].find( - candidate => candidate && typeof candidate === 'string', + isString, ); - const type = [get(entityOrRef, 'spec.type')].find( - candidate => candidate && typeof candidate === 'string', - ); + const type = [get(entityOrRef, 'spec.type')].find(isString); return { kind, namespace, name, title, description, displayName, type }; } 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..f43bba97c7 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts @@ -34,6 +34,9 @@ type FacetsInitialRequest = { text: string; }; +const maybeString = (value: unknown): string | undefined => + typeof value === 'string' ? value : undefined; + /** * This hook asynchronously loads the entity owners using the facets endpoint. * EntityOwnerPicker uses this hook when mode="owners-only" is passed as prop. @@ -52,7 +55,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 }) @@ -67,11 +70,11 @@ export function useFacetsEntities({ enabled }: { enabled: boolean }) { 'en-US', ) || ( - get(a, 'spec.profile.displayName') || + maybeString(get(a, 'spec.profile.displayName')) || a.metadata.title || a.metadata.name ).localeCompare( - get(b, 'spec.profile.displayName') || + maybeString(get(b, 'spec.profile.displayName')) || b.metadata.title || b.metadata.name, 'en-US', 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/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 b299631a21..6e254a9591 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + ## 0.0.4-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index e5f88ebba4..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.4-next.1", + "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 2c2eb03f5e..74cab9fbfb 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.8-next.0 + +### Patch Changes + +- 4f08c85: Show additional info on DevTools unprocessed entities table + + - Location path (so that it's easier to search the failed entity from the YAML URL) + - Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 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 diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 8ce8c8b0d3..ed3c14655e 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.7-next.0", + "version": "0.2.8-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", @@ -45,6 +45,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "luxon": "^3.5.0", "react-use": "^17.2.4" }, "devDependencies": { diff --git a/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx b/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx index 5d1800cb29..65e6eb731e 100644 --- a/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx +++ b/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React, { useState } from 'react'; - +import { DateTime } from 'luxon'; import { ErrorPanel, MarkdownContent, @@ -88,6 +88,24 @@ const RenderErrorContext = ({ return null; }; +/** + * Converts input datetime which lacks timezone info into user's local time so that they can + * easily understand the times. + */ +const convertTimeToLocalTimezone = (strDateTime: string | Date) => { + const dateTime = DateTime.fromFormat( + strDateTime.toLocaleString(), + 'yyyy-MM-dd hh:mm:ss', + { + zone: 'UTC', + }, + ); + + const dateTimeLocalTz = dateTime.setZone(DateTime.local().zoneName); + + return dateTimeLocalTz.toFormat('yyyy-MM-dd hh:mm:ss ZZZZ'); +}; + export const FailedEntities = () => { const classes = useStyles(); const unprocessedApi = useApi(catalogUnprocessedEntitiesApiRef); @@ -140,6 +158,13 @@ export const FailedEntities = () => { render: (rowData: UnprocessedEntity | {}) => (rowData as UnprocessedEntity).entity_ref, }, + { + title: Location Path, + sorting: true, + field: 'location_key', + render: (rowData: UnprocessedEntity | {}) => + (rowData as UnprocessedEntity).location_key, + }, { title: Kind, sorting: true, @@ -156,7 +181,25 @@ export const FailedEntities = () => { 'unknown', }, { - title: Raw, + title: Last Discovery At, + sorting: true, + field: 'last_discovery_at', + render: (rowData: UnprocessedEntity | {}) => + convertTimeToLocalTimezone( + (rowData as UnprocessedEntity).last_discovery_at, + ) || 'unknown', + }, + { + title: Next Refresh At, + sorting: true, + field: 'next_update_at', + render: (rowData: UnprocessedEntity | {}) => + convertTimeToLocalTimezone( + (rowData as UnprocessedEntity).next_update_at, + ) || 'unknown', + }, + { + title: Raw Entity Definition, sorting: false, render: (rowData: UnprocessedEntity | {}) => ( diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 48daaa7774..5aa877ad13 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/plugin-catalog +## 1.22.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @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-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + +## 1.22.1-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 5446061: The `/alpha` export no longer export extension creators for the new frontend system, existing usage should be switched to use the equivalent extension blueprint instead. For more information see the [new frontend system 1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130). +- 180a45f: Entity presentation api now only fetches fields that are required to display entity title +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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-search-common@1.2.14 + +## 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 diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 5019fc25ea..62e631dd4a 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -5,14 +5,38 @@ ```ts /// -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } 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 { 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 { FrontendPlugin } 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; + params: { + loader: () => Promise; + }; + output: ConfigurableExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: never; +}>; + // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< 'catalog', @@ -99,27 +123,7 @@ export const catalogTranslationRef: TranslationRef< >; // @alpha (undocumented) -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition< - TConfig, - TConfig, - never, - never, - string | undefined, - string | undefined, - string | undefined ->; - -// @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { catalogIndex: RouteRef; catalogEntity: RouteRef<{ @@ -141,7 +145,658 @@ const _default: BackstagePlugin< }>; unregisterRedirect: ExternalRouteRef; }, - {} + { + 'api:catalog': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'nav-item:catalog': ExtensionDefinition<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + }>; + 'api:catalog/starred-entities': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'starred-entities'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:catalog/entity-presentation': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'entity-presentation'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'entity-card:catalog/about': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'about'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/links': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'links'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/labels': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'labels'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/depends-on-components': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-components'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/depends-on-resources': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-resources'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/has-components': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-components'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/has-resources': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-resources'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/has-subcomponents': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-subcomponents'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/has-subdomains': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-subdomains'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-card:catalog/has-systems': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'has-systems'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-content:catalog/overview': ExtensionDefinition<{ + config: { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }; + output: + | 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; + } + >; + inputs: { + 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<{ + kind: 'catalog-filter'; + namespace: undefined; + name: 'tag'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'catalog-filter:catalog/kind': ExtensionDefinition<{ + config: { + initialFilter: string; + }; + configInput: { + initialFilter?: string | undefined; + }; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'catalog-filter'; + namespace: undefined; + name: 'kind'; + }>; + 'catalog-filter:catalog/type': ExtensionDefinition<{ + kind: 'catalog-filter'; + namespace: undefined; + name: 'type'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'catalog-filter:catalog/mode': ExtensionDefinition<{ + config: { + mode: 'all' | 'owners-only' | undefined; + }; + configInput: { + mode?: 'all' | 'owners-only' | undefined; + }; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'catalog-filter'; + namespace: undefined; + name: 'mode'; + }>; + 'catalog-filter:catalog/namespace': ExtensionDefinition<{ + kind: 'catalog-filter'; + namespace: undefined; + name: 'namespace'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'catalog-filter:catalog/lifecycle': ExtensionDefinition<{ + kind: 'catalog-filter'; + namespace: undefined; + name: 'lifecycle'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'catalog-filter:catalog/processing-status': ExtensionDefinition<{ + kind: 'catalog-filter'; + namespace: undefined; + name: 'processing-status'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: {}; + }>; + 'catalog-filter:catalog/list': ExtensionDefinition<{ + config: { + initialFilter: 'all' | 'owned' | 'starred'; + }; + configInput: { + initialFilter?: 'all' | 'owned' | 'starred' | undefined; + }; + output: ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'catalog-filter'; + namespace: undefined; + name: 'list'; + }>; + 'page:catalog': ExtensionDefinition<{ + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + filters: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'page'; + namespace: undefined; + name: undefined; + }>; + 'page:catalog/entity': ExtensionDefinition<{ + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + 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<{ + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + config: { + noTrack: boolean; + }; + configInput: { + noTrack?: boolean | undefined; + }; + output: ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 95333a6451..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) @@ -521,6 +526,10 @@ export function hasCatalogProcessingErrors( // @public (undocumented) export interface HasComponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -540,6 +549,10 @@ export function hasRelationWarnings( // @public (undocumented) export interface HasResourcesCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -548,6 +561,8 @@ export interface HasResourcesCardProps { // @public (undocumented) export interface HasSubcomponentsCardProps { + // (undocumented) + columns?: TableColumn[]; // (undocumented) tableOptions?: TableOptions; // (undocumented) @@ -568,6 +583,10 @@ export interface HasSubdomainsCardProps { // @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 0208271d1a..50e897be8d 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.22.0-next.2", + "version": "1.22.1-next.1", "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.ts b/plugins/catalog/src/alpha.ts deleted file mode 100644 index b78a5e6225..0000000000 --- a/plugins/catalog/src/alpha.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 * from './alpha/index'; -export { default } from './alpha/index'; -export { catalogTranslationRef } from './translation'; -export * from './translation'; 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..9e3616ff8c --- /dev/null +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx @@ -0,0 +1,110 @@ +/* + * 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, + renderInTestApp, +} 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", + "T": undefined, + "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
    )]; + }, + }); + + renderInTestApp( + createExtensionTester(extension, { config: { test: 'mock test config' } }) + .add(mockExtension) + .reactElement(), + ); + + 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/userInfo/index.ts b/plugins/catalog/src/alpha/blueprints/index.ts similarity index 90% rename from packages/backend-app-api/src/services/implementations/userInfo/index.ts rename to plugins/catalog/src/alpha/blueprints/index.ts index 13b42f053c..a3a7bcb33f 100644 --- a/packages/backend-app-api/src/services/implementations/userInfo/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 { userInfoServiceFactory } from './userInfoServiceFactory'; +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 d8a218f8f3..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, ->(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 8a18efabde..5f8ee16f87 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -15,96 +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', - filter: 'kind:component', - 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', - filter: 'kind:component', - 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', - filter: 'kind:system', - 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', - filter: 'kind:system', - 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', - filter: 'kind:component', - loader: async () => - import('../components/HasSubcomponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/HasSubcomponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSubdomainsEntityCard = createEntityCardExtension({ +export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ name: 'has-subdomains', - filter: 'kind:domain', - loader: async () => - import('../components/HasSubdomainsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSubdomainsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSystemsEntityCard = createEntityCardExtension({ +export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ name: 'has-systems', - filter: 'kind:domain', - loader: async () => - import('../components/HasSystemsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSystemsCard').then(m => + compatWrapper(), + ), + }, }); export default [ 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 100% rename from plugins/catalog/src/translation.ts rename to plugins/catalog/src/alpha/translation.ts 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 ( []; + 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/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 c11499f589..d97d6b764d 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-config-schema +## 0.1.59-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index ad98b78b98..614b80a190 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.58-next.0", + "version": "0.1.59-next.0", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index db4d04fc6e..1f7e3996bb 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,80 @@ # @backstage/plugin-devtools-backend +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 64cfc4f883..4bde07d5f7 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.9-next.2", + "version": "0.4.0-next.1", "backstage": { "role": "backend-plugin", "pluginId": "devtools", 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-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 80d0584c77..5cc72c62ae 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index d8a87753ca..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.12-next.1", + "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 afebfeefe2..4fa8e6b609 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-devtools +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @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.18-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- 019d9ad: Minor dockerfile syntax update +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/core-components@0.14.10 + - @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 + +### 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 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 23ede8144c..7fb16734f1 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -3,16 +3,79 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } 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) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, {}, - {} + { + 'api:devtools': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'page:devtools': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + 'nav-item:devtools': ExtensionDefinition<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 264f7af95b..19ec9d93c6 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.17-next.2", + "version": "0.1.18-next.1", "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 a4a4da2b17..46182e1880 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 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 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 7b1da53130..d84cb7d717 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.9-next.2", + "version": "0.4.2-next.1", "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 de55dee017..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; 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 804293b869..73e6a9c135 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## 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 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 5d7fc3f1bf..7fe1c40108 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.9-next.2", + "version": "0.2.11-next.1", "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 edef56d946..594e9a85dd 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## 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 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 a58bacdf8c..25df31c5fc 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.9-next.2", + "version": "0.2.11-next.1", "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 fcdda38e9b..f8b842415c 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + +## 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 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 91e598511f..3d1358f055 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.9-next.2", + "version": "0.2.11-next.1", "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 38b1e277e8..9f7b6ae04b 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-events-backend-module-github +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## 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 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 f77c2c9df8..877840e806 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.9-next.2", + "version": "0.2.11-next.1", "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 a7986891fa..4231baf2a8 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## 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 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 c5a0abfeb8..86a988074a 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.9-next.2", + "version": "0.2.11-next.1", "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 f408883694..5dafcc9f52 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.0 + +## 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 diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 0cff95bf35..b174bc989e 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.33-next.2", + "version": "0.1.35-next.1", "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 8c2e2f767b..14fa485178 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-events-backend +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## 0.3.12-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/config@1.2.0 + +## 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 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 01310be5d8..ee057c91fd 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.10-next.2", + "version": "0.3.12-next.1", "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 ec7c864d23..15934678d6 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-events-node +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + +## 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 diff --git a/plugins/events-node/api-report.md b/plugins/events-node/api-report.md index 729f526dee..d8a2281faa 100644 --- a/plugins/events-node/api-report.md +++ b/plugins/events-node/api-report.md @@ -4,7 +4,7 @@ ```ts import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public @@ -63,11 +63,10 @@ export interface EventsService { export type EventsServiceEventHandler = (params: EventParams) => Promise; // @public (undocumented) -export const eventsServiceFactory: ServiceFactoryCompat< +export const eventsServiceFactory: ServiceFactory< EventsService, 'plugin', - 'singleton', - undefined + 'singleton' >; // @public diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index db2a6f4271..ce31730b78 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.9-next.2", + "version": "0.4.0-next.1", "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 370053d097..3362c91a79 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,45 @@ # @internal/plugin-todo-list-backend +## 1.0.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## 1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/errors@1.2.4 + +## 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 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 b9b04c178f..cf6e61f6ab 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.30-next.2", + "version": "1.0.31-next.1", "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..d05fa54c68 100644 --- a/plugins/example-todo-list-backend/src/plugin.ts +++ b/plugins/example-todo-list-backend/src/plugin.ts @@ -26,18 +26,18 @@ import { createRouter } from './service/router'; * @public */ export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', + pluginId: 'todolist', 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 b2f2175573..4b1ba3f9ce 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + ## 1.0.21-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index cca7f5aa4b..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.21-next.1", + "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 51e2b93ef9..b31862e9b3 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/plugin-todo-list +## 1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 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 diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index d950d8c406..df2fe42a29 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.30-next.0", + "version": "1.0.31-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index d810195e7f..41a71ed633 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-home-react +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 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 diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 00b0f29fca..1af7a32d86 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.16-next.0", + "version": "0.1.17-next.0", "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 08cf9bec5f..51c5abe7dd 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/plugin-home +## 0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.17-next.0 + +## 0.7.10-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + +## 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 diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index 0f9b980a78..7b4b157eb2 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -3,11 +3,68 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } 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 { FrontendPlugin } 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: FrontendPlugin< + {}, + {}, + { + 'page:home': ExtensionDefinition<{ + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + 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) diff --git a/plugins/home/package.json b/plugins/home/package.json index e566b46e78..7204942d52 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.9-next.2", + "version": "0.7.10-next.1", "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 b13c2a2e0e..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'; @@ -35,37 +35,40 @@ 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/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 1255d6d0fd..33447a6be2 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/plugin-kubernetes-backend +## 0.18.6-next.1 + +### Patch Changes + +- ca96b66: Skip start without proper config +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.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-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-node@0.1.19-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.18.6-next.0 + +### Patch Changes + +- f55f8bf: The `KubernetesBuilder` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-kubernetes-node@0.1.19-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.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-common@0.8.2 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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/package.json b/plugins/kubernetes-backend/package.json index 70f640e0ba..dc42a2b7b2 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.4-next.2", + "version": "0.18.6-next.1", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", @@ -99,6 +99,7 @@ }, "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/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index 57221e377c..284fa2e5e2 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -193,27 +193,33 @@ export const kubernetesPlugin = createBackendPlugin({ auth, httpAuth, }) { - // TODO: expose all of the customization & extension points of the builder here - const builder: KubernetesBuilder = KubernetesBuilder.createBuilder({ - logger, - config, - catalogApi, - permissions, - discovery, - auth, - httpAuth, - }) - .setObjectsProvider(extPointObjectsProvider.getObjectsProvider()) - .setClusterSupplier(extPointClusterSuplier.getClusterSupplier()) - .setFetcher(extPointFetcher.getFetcher()) - .setServiceLocator(extPointServiceLocator.getServiceLocator()); + if (config.has('kubernetes')) { + // TODO: expose all of the customization & extension points of the builder here + const builder: KubernetesBuilder = KubernetesBuilder.createBuilder({ + logger, + config, + catalogApi, + permissions, + discovery, + auth, + httpAuth, + }) + .setObjectsProvider(extPointObjectsProvider.getObjectsProvider()) + .setClusterSupplier(extPointClusterSuplier.getClusterSupplier()) + .setFetcher(extPointFetcher.getFetcher()) + .setServiceLocator(extPointServiceLocator.getServiceLocator()); - AuthStrategy.addAuthStrategiesFromArray( - extPointAuthStrategy.getAuthenticationStrategies(), - builder, - ); - const { router } = await builder.build(); - http.use(router); + AuthStrategy.addAuthStrategiesFromArray( + extPointAuthStrategy.getAuthenticationStrategies(), + builder, + ); + const { router } = await builder.build(); + http.use(router); + } else { + logger.warn( + 'Failed to initialize kubernetes backend: valid kubernetes config is missing', + ); + } }, }); }, 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 5edb8845d4..3299d11e5d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -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; @@ -728,6 +728,19 @@ metadata: const throwError = () => startTestBackend({ features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { type: 'multiTenant' }, + clusterLocatorMethods: [ + { + type: 'config', + clusters: [], + }, + ], + }, + }, + }), import('@backstage/plugin-kubernetes-backend/alpha'), createBackendModule({ pluginId: 'kubernetes', 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/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 801c2018c9..e5f1848841 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## 0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.2 + +## 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 diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 75c0758ac5..29c933be61 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.14-next.2", + "version": "0.0.15-next.1", "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 f8ac4b4768..6b3606335c 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 1e05ed873e..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.2-next.1", + "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 42e5885c49..8259430ff5 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-kubernetes-node +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 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 diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 5377f26b4d..cc6aa3dd65 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.17-next.2", + "version": "0.1.19-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", @@ -49,6 +49,7 @@ "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 24cd459fde..65e48affc0 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ClusterDetails } from '../types'; import { mockServices, @@ -37,6 +36,7 @@ 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; diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 34e9b259be..747360d3e4 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-kubernetes-react +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @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 + +### 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 diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index dbf89ae0cf..8d60446945 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.2-next.2", + "version": "0.4.3-next.0", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", 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 1b5ca07506..07a229d934 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-kubernetes +## 0.11.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## 0.11.14-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- bfc0f42: Make k8s entity content appear on components & resources only by default in new FE system +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.2 + +## 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 diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index 4ffde719f2..380c1dffbd 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -52,7 +52,7 @@ export const app = createApp({ }); ``` -2. Next, enable your desired extensions in `app-config.yaml` +2. Next, enable your desired extensions in `app-config.yaml`. ```yaml app: @@ -60,4 +60,14 @@ app: - entity-content:kubernetes/kubernetes ``` -Now, the extension appears on your entity page as one of the tabs, which is called `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 index befbcf379a..852a5c6c1a 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -3,16 +3,144 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +/// + +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } 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 { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { kubernetes: RouteRef; }, {}, - {} + { + 'api:kubernetes': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'page:kubernetes': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + 'entity-content:kubernetes/kubernetes': ExtensionDefinition<{ + kind: 'entity-content'; + namespace: undefined; + name: 'kubernetes'; + config: { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'api:kubernetes/proxy': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'proxy'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:kubernetes/auth-providers': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'auth-providers'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'api:kubernetes/cluster-link-formatter': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: 'cluster-link-formatter'; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index ab8fe84d9b..9020737fbd 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.11.13-next.2", + "version": "0.11.14-next.1", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes/src/alpha/apis.tsx b/plugins/kubernetes/src/alpha/apis.tsx index 6c880d2f53..082a2da6c7 100644 --- a/plugins/kubernetes/src/alpha/apis.tsx +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -15,7 +15,7 @@ */ import { - createApiExtension, + ApiBlueprint, createApiFactory, discoveryApiRef, fetchApiRef, @@ -40,80 +40,91 @@ import { oneloginAuthApiRef, } from '@backstage/core-plugin-api'; -export const kubernetesApiExtension = createApiExtension({ - factory: createApiFactory({ - api: kubernetesApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, - }, - factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => - new KubernetesBackendClient({ - discoveryApi, - fetchApi, - kubernetesAuthProvidersApi, - }), - }), +export const 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 = createApiExtension({ - factory: createApiFactory({ - api: kubernetesProxyApiRef, - deps: { - kubernetesApi: kubernetesApiRef, - }, - factory: ({ kubernetesApi }) => - new KubernetesProxyClient({ - kubernetesApi, - }), - }), +export const kubernetesProxyApi = ApiBlueprint.make({ + name: 'proxy', + params: { + factory: createApiFactory({ + api: kubernetesProxyApiRef, + deps: { + kubernetesApi: kubernetesApiRef, + }, + factory: ({ kubernetesApi }) => + new KubernetesProxyClient({ + kubernetesApi, + }), + }), + }, }); -export const kubernetesAuthProvidersApi = createApiExtension({ - factory: createApiFactory({ - api: kubernetesAuthProvidersApiRef, - deps: { - gitlabAuthApi: gitlabAuthApiRef, - googleAuthApi: googleAuthApiRef, - microsoftAuthApi: microsoftAuthApiRef, - oktaAuthApi: oktaAuthApiRef, - oneloginAuthApi: oneloginAuthApiRef, - }, - factory: ({ - gitlabAuthApi, - googleAuthApi, - microsoftAuthApi, - oktaAuthApi, - oneloginAuthApi, - }) => { - const oidcProviders = { - gitlab: gitlabAuthApi, - google: googleAuthApi, - microsoft: microsoftAuthApi, - okta: oktaAuthApi, - onelogin: oneloginAuthApi, - }; - - return new KubernetesAuthProviders({ - microsoftAuthApi, +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, - oidcProviders, - }); - }, - }), + microsoftAuthApi, + oktaAuthApi, + oneloginAuthApi, + }) => { + const oidcProviders = { + gitlab: gitlabAuthApi, + google: googleAuthApi, + microsoft: microsoftAuthApi, + okta: oktaAuthApi, + onelogin: oneloginAuthApi, + }; + + return new KubernetesAuthProviders({ + microsoftAuthApi, + googleAuthApi, + oidcProviders, + }); + }, + }), + }, }); -export const kubernetesClusterLinkFormatterApi = createApiExtension({ - factory: createApiFactory({ - api: kubernetesClusterLinkFormatterApiRef, - deps: { googleAuthApi: googleAuthApiRef }, - factory: deps => { - const formatters = getDefaultFormatters(deps); - return new KubernetesClusterLinkFormatter({ - formatters, - defaultFormatterName: DEFAULT_FORMATTER_NAME, - }); - }, - }), +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 index 9ad0b6843f..2703fcdfc2 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -15,20 +15,18 @@ */ import React from 'react'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; -import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; -import { rootCatalogKubernetesRouteRef } from '../plugin'; +import { compatWrapper } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; -export const entityKubernetesContent = createEntityContentExtension({ - defaultPath: 'kubernetes', - defaultTitle: 'Kubernetes', +export const entityKubernetesContent = EntityContentBlueprint.make({ name: 'kubernetes', - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - loader: () => - import('./KubernetesContentPage').then(m => - compatWrapper(), - ), + params: { + defaultPath: '/kubernetes', + defaultTitle: 'Kubernetes', + filter: 'kind:component,resource', + loader: () => + import('./KubernetesContentPage').then(m => + compatWrapper(), + ), + }, }); diff --git a/plugins/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx index 8b5db150bd..a58ffec94f 100644 --- a/plugins/kubernetes/src/alpha/pages.tsx +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -16,18 +16,20 @@ import React from 'react'; // Add this line to import React -import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { rootCatalogKubernetesRouteRef } from '../plugin'; -export const kubernetesPage = createPageExtension({ - defaultPath: '/kubernetes', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - // these inputs usually match the props required by the component. - loader: () => import('../Router').then(m => compatWrapper()), +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 index 47433bce35..1369133a24 100644 --- a/plugins/kubernetes/src/alpha/plugin.tsx +++ b/plugins/kubernetes/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 { kubernetesPage } from './pages'; import { entityKubernetesContent } from './entityContents'; import { rootCatalogKubernetesRouteRef } from '../plugin'; @@ -26,7 +26,7 @@ import { kubernetesProxyApi, } from './apis'; -export default createPlugin({ +export default createFrontendPlugin({ id: 'kubernetes', extensions: [ kubernetesPage, diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index a0b4bc8caa..113c7b06b4 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,124 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.0-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.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 + +## 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 diff --git a/plugins/notifications-backend-module-email/api-report.md b/plugins/notifications-backend-module-email/api-report.md index b105f15cba..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,7 +17,7 @@ export interface NotificationsEmailTemplateExtensionPoint { export const notificationsEmailTemplateExtensionPoint: ExtensionPoint; // @public (undocumented) -const notificationsModuleEmail: BackendFeatureCompat; +const notificationsModuleEmail: BackendFeature; export default notificationsModuleEmail; // @public (undocumented) diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 33451341ea..9b910b0041 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.2.0-next.2", + "version": "0.3.0-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -42,6 +42,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration-aws-node": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts index 6b74bf70e7..c83d27b6a3 100644 --- a/plugins/notifications-backend-module-email/src/module.ts +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -17,7 +17,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { NotificationsEmailProcessor } from './processor'; import { @@ -46,21 +46,17 @@ export const notificationsModuleEmail = createBackendModule({ deps: { config: coreServices.rootConfig, notifications: notificationsProcessingExtensionPoint, - discovery: coreServices.discovery, logger: coreServices.logger, auth: coreServices.auth, cache: coreServices.cache, + catalog: catalogServiceRef, }, - async init({ config, notifications, discovery, logger, auth, cache }) { - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); - + async init({ config, notifications, logger, auth, cache, catalog }) { notifications.addProcessor( new NotificationsEmailProcessor( logger, config, - catalogClient, + catalog, auth, cache, templateRenderer, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 03388a77a2..9337915ecc 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -24,10 +24,7 @@ import { } from '@backstage/backend-plugin-api'; import { Config, readDurationFromConfig } from '@backstage/config'; import { durationToMilliseconds } from '@backstage/types'; -import { - CATALOG_FILTER_EXISTS, - CatalogClient, -} from '@backstage/catalog-client'; +import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client'; import { getProcessorFiltersFromConfig, Notification, @@ -63,7 +60,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { constructor( private readonly logger: LoggerService, private readonly config: Config, - private readonly catalog: CatalogClient, + private readonly catalog: CatalogApi, private readonly auth: AuthService, private readonly cache?: CacheService, private readonly templateRenderer?: NotificationTemplateRenderer, @@ -207,12 +204,17 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private async getRecipientEmails( notification: Notification, options: NotificationSendOptions, - ) { + ): Promise { let emails: string[]; - if (options.recipients.type === 'broadcast' || notification.user === null) { + if (options.recipients.type === 'broadcast') { emails = await this.getBroadcastEmails(); - } else { + } 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 []; } if (this.allowlistEmailAddresses) { @@ -338,6 +340,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/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 61cba9e3a6..b74489313d 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-notifications-backend +## 0.4.0-next.1 + +### Patch Changes + +- f195972: Validate notification link when new notification is created +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-events-node@0.4.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-notifications-common@0.0.5 + +## 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 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 9bc26f7a1f..d91f0f7aea 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.4-next.2", + "version": "0.4.0-next.1", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -45,6 +45,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", 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/plugin.ts b/plugins/notifications-backend/src/plugin.ts index c0344fb06f..84a200bae1 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -25,6 +25,7 @@ import { notificationsProcessingExtensionPoint, NotificationsProcessingExtensionPoint, } from '@backstage/plugin-notifications-node'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; class NotificationsProcessingExtensionPointImpl implements NotificationsProcessingExtensionPoint @@ -65,8 +66,9 @@ export const notificationsPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, logger: coreServices.logger, database: coreServices.database, - discovery: coreServices.discovery, signals: signalsServiceRef, + config: coreServices.rootConfig, + catalog: catalogServiceRef, }, async init({ auth, @@ -75,8 +77,9 @@ export const notificationsPlugin = createBackendPlugin({ httpRouter, logger, database, - discovery, signals, + config, + catalog, }) { httpRouter.use( await createRouter({ @@ -84,8 +87,9 @@ export const notificationsPlugin = createBackendPlugin({ httpAuth, userInfo, logger, + config, database, - discovery, + catalog, signals, processors: processingExtensions.processors, }), diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts index fd564d6528..9ced24709b 100644 --- a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts @@ -157,5 +157,5 @@ export const getUsersForEntityRef = async ( users.push(...u); } - return [...new Set(users)]; + return [...new Set(users)].filter(Boolean); }; diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 804b0ff088..b90ebcaf50 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { DatabaseManager, PluginDatabaseManager, @@ -23,7 +24,9 @@ import request from 'supertest'; import { createRouter } from './router'; import { ConfigReader } from '@backstage/config'; import { SignalsService } from '@backstage/plugin-signals-node'; -import { mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { NotificationSendOptions } from '@backstage/plugin-notifications-node'; +import { CatalogClient } from '@backstage/catalog-client'; function createDatabase(): PluginDatabaseManager { return DatabaseManager.fromConfig( @@ -45,20 +48,28 @@ describe('createRouter', () => { publish: jest.fn(), }; - const discovery = mockServices.discovery(); const userInfo = mockServices.userInfo(); - const httpAuth = mockServices.httpAuth(); + const httpAuth = mockServices.httpAuth({ + defaultCredentials: mockCredentials.service(), + }); const auth = mockServices.auth(); + const config = mockServices.rootConfig({ + data: { app: { baseUrl: 'http://localhost' } }, + }); + const catalog = new CatalogClient({ + discoveryApi: mockServices.discovery.mock(), + }); beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), database: createDatabase(), - discovery, signals: signalService, userInfo, + config, httpAuth, auth, + catalog, }); app = express().use(router); }); @@ -75,4 +86,80 @@ describe('createRouter', () => { expect(response.body).toEqual({ status: 'ok' }); }); }); + + describe('POST /', () => { + const sendNotification = async (data: NotificationSendOptions) => + request(app) + .post('/') + .send(data) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + it('returns error on invalid link', async () => { + const javascriptXSS = await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + // eslint-disable-next-line no-script-url + link: 'javascript:alert(document.domain)', + }, + }); + + expect(javascriptXSS.status).toEqual(400); + + const ftpLink = await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + link: 'ftp://example.com', + }, + }); + + expect(ftpLink.status).toEqual(400); + }); + + it('should accept absolute http links', async () => { + const httpLink = await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + link: 'http://localhost/test', + }, + }); + + expect(httpLink.status).toEqual(200); + + const httpsLink = await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + link: 'https://example.com', + }, + }); + + expect(httpsLink.status).toEqual(200); + }); + + it('should accept relative links', async () => { + const catalogLink = await sendNotification({ + recipients: { + type: 'broadcast', + }, + payload: { + title: 'test notification', + link: '/catalog', + }, + }); + + expect(catalogLink.status).toEqual(200); + }); + }); }); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index abd7e2dc13..fb5aacb5f4 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common'; import express, { Request } from 'express'; import Router from 'express-promise-router'; @@ -22,7 +23,7 @@ import { NotificationGetOptions, } from '../database'; import { v4 as uuid } from 'uuid'; -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi } from '@backstage/catalog-client'; import { NotificationProcessor, NotificationSendOptions, @@ -30,7 +31,6 @@ import { import { InputError } from '@backstage/errors'; import { AuthService, - DiscoveryService, HttpAuthService, LoggerService, UserInfoService, @@ -46,17 +46,18 @@ import { } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; import { getUsersForEntityRef } from './getUsersForEntityRef'; +import { Config } from '@backstage/config'; /** @internal */ export interface RouterOptions { logger: LoggerService; + config: Config; database: PluginDatabaseManager; - discovery: DiscoveryService; auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService; signals?: SignalsService; - catalog?: CatalogApi; + catalog: CatalogApi; processors?: NotificationProcessor[]; } @@ -65,20 +66,19 @@ export async function createRouter( options: RouterOptions, ): Promise { const { + config, logger, database, auth, httpAuth, userInfo, - discovery, catalog, processors = [], signals, } = options; - const catalogClient = - catalog ?? new CatalogClient({ discoveryApi: discovery }); const store = await DatabaseNotificationsStore.create({ database }); + const frontendBaseUrl = config.getString('app.baseUrl'); const getUser = async (req: Request) => { const credentials = await httpAuth.credentials(req, { allow: ['user'] }); @@ -177,6 +177,18 @@ export async function createRouter( } }; + const validateLink = (link: string) => { + const stripLeadingSlash = (s: string) => s.replace(/^\//, ''); + const ensureTrailingSlash = (s: string) => s.replace(/\/?$/, '/'); + const url = new URL( + stripLeadingSlash(link), + ensureTrailingSlash(frontendBaseUrl), + ); + if (url.protocol !== 'https:' && url.protocol !== 'http:') { + throw new Error('Only HTTP/HTTPS links are allowed'); + } + }; + // TODO: Move to use OpenAPI router instead const router = Router(); router.use(express.json()); @@ -419,13 +431,21 @@ export async function createRouter( allow: ['service'], }); - const { title } = payload; + const { title, link } = payload; if (!recipients || !title) { logger.error(`Invalid notification request received`); throw new InputError(`Invalid notification request received`); } + if (link) { + try { + validateLink(link); + } catch (e) { + throw new InputError('Invalid link provided', e); + } + } + const origin = credentials.principal.subject; const baseNotification = { payload: { @@ -450,7 +470,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], - { auth, catalogClient }, + { auth, catalogClient: catalog }, ); } catch (e) { logger.error(`Failed to resolve notification receivers: ${e}`); diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 627cb53ea0..7066316a04 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-notifications-node +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-signals-node@0.1.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-notifications-common@0.0.5 + +## 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 diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 533ebe7ce2..09008239ae 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.4-next.2", + "version": "0.2.6-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 42c55615b2..945000c468 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-notifications +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-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.5-next.0 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-react@0.0.5-next.0 + - @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 + +## 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 diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 5dbfd83255..7dd72d0d97 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.4-next.0", + "version": "0.3.1-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 457893b016..22ce11db69 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -111,7 +111,7 @@ 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, requestUserPermission } = useWebNotifications( @@ -126,7 +126,7 @@ export const NotificationsSidebarItem = (props?: { <> { if (notification.payload.link) { notificationsApi @@ -189,7 +189,6 @@ export const NotificationsSidebarItem = (props?: { ) { return; } - notificationsApi .getNotification(signal.notification_id) .then(notification => { @@ -211,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, @@ -273,7 +273,7 @@ export const NotificationsSidebarItem = (props?: { /> )} { requestUserPermission(); }} 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/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index ec120a614d..cc69a0e815 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -14,18 +14,16 @@ * limitations under the License. */ import { useCallback, useState } from 'react'; -import { rootRouteRef } from '../routes'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { useNavigate } from 'react-router-dom'; import { notificationsApiRef } from '../api'; +import { rootRouteRef } from '../routes'; /** @internal */ export function useWebNotifications(enabled: boolean) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); - const notificationsRoute = useRouteRef(rootRouteRef); + const notificationsRoute = useRouteRef(rootRouteRef)(); const notificationsApi = useApi(notificationsApiRef); - const navigate = useNavigate(); const requestUserPermission = useCallback(() => { if ( @@ -64,14 +62,14 @@ export function useWebNotifications(enabled: boolean) { read: true, }); } else { - navigate(notificationsRoute()); + window.open(notificationsRoute); } notification.close(); }; return notification; }, - [webNotificationPermission, notificationsApi, navigate, notificationsRoute], + [webNotificationPermission, notificationsApi, notificationsRoute], ); return { sendWebNotification, requestUserPermission }; diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 4bbf343849..034384a4be 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-org-react +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## 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 diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index d91d73c4ca..b0e983b324 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.27-next.2", + "version": "0.1.28-next.1", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 923d5d1d23..a273d086a1 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-org +## 0.6.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.6.29-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## 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 diff --git a/plugins/org/api-report-alpha.md b/plugins/org/api-report-alpha.md index be74cde141..a8c6bd70ec 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -3,16 +3,149 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { 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 { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< {}, { catalogIndex: ExternalRouteRef; }, - {} + { + 'entity-card:org/group-profile': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'group-profile'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:org/members-list': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'members-list'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:org/ownership': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'ownership'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + 'entity-card:org/user-profile': ExtensionDefinition<{ + kind: 'entity-card'; + namespace: undefined; + name: 'user-profile'; + config: { + filter: string | undefined; + }; + configInput: { + filter?: string | undefined; + }; + output: + | 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; + } + >; + inputs: {}; + }>; + } >; export default _default; diff --git a/plugins/org/package.json b/plugins/org/package.json index 44ae3d10f6..93957e54e1 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.28-next.2", + "version": "0.6.29-next.1", "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/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 7c0765ad43..1534a2d605 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 0ba16a61f7..866e2fe744 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.20-next.2", + "version": "0.2.0-next.1", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 89955c0f69..a1008cf37c 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-permission-backend +## 0.5.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## 0.5.49-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- fcb9356: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1 + +## 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 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 7a331c1ccd..282af7cc0b 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.47-next.2", + "version": "0.5.49-next.1", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index fd33f0b629..db2b72f95c 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -42,7 +42,6 @@ import { import { PermissionIntegrationClient } from './PermissionIntegrationClient'; import { memoize } from 'lodash'; import DataLoader from 'dataloader'; -import { Config } from '@backstage/config'; import { AuthService, BackstageCredentials, @@ -51,6 +50,7 @@ import { DiscoveryService, HttpAuthService, LoggerService, + RootConfigService, UserInfoService, } from '@backstage/backend-plugin-api'; @@ -97,13 +97,14 @@ const evaluatePermissionRequestBatchSchema: z.ZodSchema { 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(), }); @@ -101,7 +100,6 @@ describe('ServerPermissionClient', () => { 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(), }); @@ -115,7 +113,6 @@ describe('ServerPermissionClient', () => { 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(), }); @@ -156,7 +153,6 @@ 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(), }); @@ -173,7 +169,6 @@ describe('ServerPermissionClient', () => { 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(), }); @@ -190,7 +185,6 @@ describe('ServerPermissionClient', () => { 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(), }); @@ -223,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 e21067bfa1..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( diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 83f4dea7ed..b92bf66ce8 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 394f56ff02..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.25-next.1", + "version": "0.4.25", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 138b98902b..07cd16bbdd 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-proxy-backend +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.6-next.0 + +### Patch Changes + +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- d298e6e: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 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 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 091e637886..c23ea0fca4 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.4-next.2", + "version": "0.5.6-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index ac32179ef3..a48b18e8e2 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,10 +14,8 @@ * 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, registerMswTestHooks, @@ -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 90541db310..f3bb21a181 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 92d64ec7f0..d76df5f0eb 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.15-next.2", + "version": "0.2.0-next.1", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 604b44d3da..391eaed947 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + +## 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 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 1806189dd4..316dcc867c 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.13-next.2", + "version": "0.2.0-next.1", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.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-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 221a626169..5bbe594290 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 5c08e23b6f..a6650512e0 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.13-next.2", + "version": "0.2.0-next.1", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 062d54226d..e68474476c 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 a9f499ec74..56596b59bc 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.13-next.2", + "version": "0.3.0-next.1", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 2621b1d8f3..9c945e4faf 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 b4b10f7d4c..05383eb557 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.24-next.2", + "version": "0.3.0-next.1", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts index 36ae750723..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,7 +14,7 @@ * 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 { @@ -28,6 +28,7 @@ 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`; @@ -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 4c3c6b891f..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,7 +15,7 @@ */ 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 { @@ -27,6 +27,7 @@ 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`; @@ -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 160953da23..e41bf64fdc 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + +## 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 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 7749830304..48a106d5db 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.47-next.2", + "version": "0.3.0-next.1", "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:^", 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 index ae46ef1bd6..73c17d8ac0 100644 --- 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 @@ -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'; @@ -25,6 +25,7 @@ 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(); @@ -71,7 +72,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.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 efbe0a07cf..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'; @@ -140,7 +143,7 @@ export class CookiecutterRunner { * @public */ export function createFetchCookiecutterAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; }) { diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index c759549d90..4eaf48a537 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 33459ba5b6..f0a5114dc0 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.1-next.2", + "version": "0.2.0-next.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 811e9a80cc..1dd8dff942 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 9e5923d: Added test cases for publish:gerrit action examples +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 0e50124013..b09b9101b9 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.15-next.2", + "version": "0.2.0-next.1", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.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-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 0a05340f77..f3474b406e 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 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 cb888b65ad..3269b54a8d 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.13-next.2", + "version": "0.2.0-next.1", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index e9d841aa0c..5c93531f01 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 4ec5795dd9..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'; @@ -424,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 c373839d13..2664a1a667 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.1-next.2", + "version": "0.5.0-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index f62e5b4db8..c8875fe3ea 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + +## 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 diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index ab89669ede..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'; @@ -255,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 dde6e48a6e..6b711c0f5f 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.5-next.2", + "version": "0.5.0-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", 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 3eda50a924..46e60584c9 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -408,7 +408,7 @@ describe('createGitLabMergeRequest', () => { 'owner/repo', 'new-mr', 'Create my new MR', - [ + expect.arrayContaining([ { action: 'create', filePath: 'irrelevant/bar.txt', @@ -423,7 +423,7 @@ describe('createGitLabMergeRequest', () => { content: 'SGVsbG8gdGhlcmUh', execute_filemode: false, }, - ], + ]), ); }); }); @@ -450,7 +450,7 @@ describe('createGitLabMergeRequest', () => { 'owner/repo', 'new-mr', 'Create my new MR', - [ + expect.arrayContaining([ { action: 'update', filePath: 'source/auto.txt', @@ -465,7 +465,7 @@ describe('createGitLabMergeRequest', () => { encoding: 'base64', execute_filemode: false, }, - ], + ]), ); }); }); @@ -562,7 +562,7 @@ describe('createGitLabMergeRequest', () => { 'owner/repo', 'new-mr', 'Create my new MR', - [ + expect.arrayContaining([ { action: 'update', filePath: 'source/auto.txt', @@ -577,7 +577,7 @@ describe('createGitLabMergeRequest', () => { encoding: 'base64', execute_filemode: false, }, - ], + ]), ); }); @@ -604,7 +604,7 @@ describe('createGitLabMergeRequest', () => { 'owner/repo', 'new-mr', 'Create my new MR', - [ + expect.arrayContaining([ { action: 'update', filePath: 'source/auto.txt', @@ -619,7 +619,7 @@ describe('createGitLabMergeRequest', () => { encoding: 'base64', execute_filemode: false, }, - ], + ]), ); }); diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 85da251d02..4a3d53c896 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,58 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.1.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/plugin-notifications-common@0.0.5 + +## 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 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 204bf597e9..65b8da39dd 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.6-next.2", + "version": "0.1.0-next.1", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index ed34f47a58..651ad36d61 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.5.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + +## 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 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 8cbd5e1067..09e0f0dc93 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.40-next.2", + "version": "0.5.0-next.1", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", 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 index 37e678ab6a..6bc84e04ad 100644 --- 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 @@ -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'; @@ -37,6 +37,7 @@ 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(); @@ -66,7 +67,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.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 94a2a3539b..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'; @@ -28,6 +28,7 @@ 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. @@ -40,7 +41,7 @@ import { examples } from './index.examples'; * @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 */ diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index c4421d314b..0a98cd391f 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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 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 f683e23d09..f0935fbb05 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.31-next.2", + "version": "0.2.0-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 4833302170..35f01d25c6 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.1 + +## 0.4.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.0 + +## 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 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 36aed409cc..6a5599f6a4 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.7-next.2", + "version": "0.4.0-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 0ed359768e..36f4facf93 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,145 @@ # @backstage/plugin-scaffolder-backend +## 1.25.0-next.1 + +### Patch Changes + +- f865103: Updated dependency `esbuild` to `^0.23.0`. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 1.25.0-next.0 + +### Minor Changes + +- 62898bd: `createRouter` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. + +### Patch Changes + +- c160951: Found the issue during testing the clean up of the workspace for the database implementation. +- d425fc4: Modules, plugins, and services are now `BackendFeature`, not a function that returns a feature. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.0 + - @backstage/plugin-auth-node@0.5.2-next.0 + - @backstage/plugin-catalog-node@1.12.7-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 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 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 1e60d5b026..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; @@ -320,7 +321,7 @@ export const createPublishGitlabMergeRequestAction: (options: { JsonObject >; -// @public +// @public @deprecated export function createRouter(options: RouterOptions): Promise; // @public @deprecated (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) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 974125ad94..4017dc760b 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.1-next.2", + "version": "1.25.0-next.1", "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:^", @@ -122,7 +124,7 @@ "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", - "esbuild": "^0.21.0", + "esbuild": "^0.23.0", "strip-ansi": "^7.1.0", "supertest": "^6.1.3", "wait-for-expect": "^3.0.2" 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/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index ba675b2cc7..23fe793432 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -532,8 +532,8 @@ export class DatabaseTaskStore implements TaskStore { } async cleanWorkspace({ taskId }: { taskId: string }): Promise { - await this.db('tasks').where({ id: taskId }).update({ - workspace: undefined, + await this.db('tasks').where({ id: taskId }).update({ + workspace: null, }); } @@ -542,10 +542,11 @@ export class DatabaseTaskStore implements TaskStore { taskId: string; }): Promise { if (options.path) { + const workspace = (await serializeWorkspace(options)).contents; await this.db('tasks') .where({ id: options.taskId }) .update({ - workspace: (await serializeWorkspace(options)).contents, + workspace, }); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseWorkspaceProvider.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseWorkspaceProvider.ts index 69f90b3d7e..b3c3c24975 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseWorkspaceProvider.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseWorkspaceProvider.ts @@ -29,7 +29,7 @@ export class DatabaseWorkspaceProvider implements WorkspaceProvider { path: string; taskId: string; }): Promise { - this.storage.serializeWorkspace?.(options); + await this.storage.serializeWorkspace?.(options); } public async rehydrateWorkspace(options: { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 3b88fd0f10..09fc162ea6 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, @@ -508,11 +509,11 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const output = this.render(task.spec.output, context, renderTemplate); await taskTrack.markSuccessful(); + await task.cleanWorkspace?.(); return { output }; } finally { if (workspacePath) { - await task.cleanWorkspace?.(); await fs.remove(workspacePath); } } @@ -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/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 01a2fba6e8..9673d19255 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'; @@ -45,9 +44,13 @@ import { AuthorizeResult, PermissionEvaluator, } from '@backstage/plugin-permission-common'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { + mockCredentials, + mockErrorHandler, + mockServices, +} from '@backstage/backend-test-utils'; import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; -import { MiddlewareFactory } from '@backstage/backend-app-api'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; const mockAccess = jest.fn(); @@ -1501,8 +1504,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ results: [{ title: 'blob' }], }); - const logger = mockServices.logger.mock(); - const middleware = MiddlewareFactory.create({ config, logger }); const router = await createRouter({ logger: loggerToWinstonLogger(mockServices.logger.mock()), config: new ConfigReader({}), @@ -1519,7 +1520,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }, }); - app = express().use(router).use(middleware.error()); + app = express().use(router).use(mockErrorHandler()); }); it('should throw an error when the provider is not registered', async () => { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index a25731ce45..3eec4e4450 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -17,10 +17,7 @@ import { createLegacyAuthAdapters, HostDiscovery, - PluginDatabaseManager, - UrlReader, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, @@ -88,6 +85,9 @@ import { HttpAuthService, LifecycleService, PermissionsService, + UrlReaderService, + SchedulerService, + DatabaseService, } from '@backstage/backend-plugin-api'; import { IdentityApi, @@ -140,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 @@ -258,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, diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 4810656ef4..7e121a4e93 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 949a831906..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.5-next.1", + "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 c7d37fb0c8..fef8549682 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-test-utils@0.6.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.0 + - @backstage/backend-test-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.0 + - @backstage/types@1.1.1 + +## 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 diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 707ffa1388..4b32bf3d95 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.10-next.2", + "version": "0.1.12-next.1", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 42fc13a644..887fac33d1 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-scaffolder-node +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 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 diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 515a169cb2..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; diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 6e84b278c2..42291dd081 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.9-next.2", + "version": "0.4.11-next.1", "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-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 1661f3d1c7..991cc8d10e 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-scaffolder-react +## 1.12.0-next.1 + +### Patch Changes + +- c2cbe1e: Updated dependency `use-immer` to `^0.10.0`. +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.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 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 1.12.0-next.0 + +### Minor Changes + +- 4512f71: Add `ui:backstage.review.name` option for custom item names on scaffolder review page, and also add support for rendering the `title` property instead of the key name. + +### Patch Changes + +- 3ebb64f: - Fix secret widget field not displaying as required. + - Fix secret widget not able to be required inside nested objects. + - Fix secret widget not able to be disabled. + - Support `minLength` and `maxLength` properties for secret widget. +- 8dd6ef6: Fix an issue where keys with duplicate final key parts are not all displayed in the `ReviewState`. Change the way the keys are formatted to include the full schema path, separated by `>`. +- 9a0672a: Scaffolder review page shows static amount of asterisks for secret fields. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @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 + +### 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 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/package.json b/plugins/scaffolder-react/package.json index b47bbd731f..d952986e6e 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.11.0-next.2", + "version": "1.12.0-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", @@ -88,7 +88,7 @@ "luxon": "^3.0.0", "qs": "^6.9.4", "react-use": "^17.2.4", - "use-immer": "^0.9.0", + "use-immer": "^0.10.0", "zen-observable": "^0.10.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" 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 194c5f3e77..a3fcecbe2b 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx @@ -170,7 +170,8 @@ describe('ReviewState', () => { ); expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument(); - expect(getByRole('row', { name: 'Foo 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 () => { @@ -219,7 +220,9 @@ describe('ReviewState', () => { expect( queryByRole('row', { name: 'Name Label-type2' }), ).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Foo Label-type2' })).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Nest > Foo Label-type2' }), + ).toBeInTheDocument(); }); it('should display enum value if no corresponding enumNames', async () => { @@ -292,8 +295,12 @@ describe('ReviewState', () => { , ); - expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); + 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 () => { @@ -303,6 +310,7 @@ describe('ReviewState', () => { bar: 'type4', example: { test: 'type6', + foo: 'type7', }, }, }; @@ -329,6 +337,9 @@ describe('ReviewState', () => { test: { type: 'string', }, + foo: { + type: 'string', + }, }, }, }, @@ -345,9 +356,18 @@ describe('ReviewState', () => { , ); - expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Test type6' })).toBeInTheDocument(); + 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 () => { @@ -404,8 +424,77 @@ describe('ReviewState', () => { , ); - expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); - expect(queryByRole('row', { name: 'Test type6' })).not.toBeInTheDocument(); + 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(); + }); + + it('should allow using the title property', async () => { + const formState = { + foo: 'test', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + foo: { + type: 'string', + title: 'Test Thing', + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Test Thing test' })).toBeInTheDocument(); + }); + + it('should allow custom review name', async () => { + const formState = { + foo: 'test', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + foo: { + type: 'string', + 'ui:backstage': { + review: { + name: 'bar', + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Bar test' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Foo test' })).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 d87584afff..7d2c6042dc 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -18,7 +18,7 @@ import { StructuredMetadataTable } from '@backstage/core-components'; import { JsonObject, JsonValue } from '@backstage/types'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema'; -import { isJsonObject, getLastKey } from './util'; +import { isJsonObject, formatKey } from './util'; /** * The props for the {@link ReviewState} component. @@ -41,25 +41,33 @@ function processSchema( data: formState, }); + const name = + definitionInSchema?.['ui:backstage']?.review?.name ?? + definitionInSchema?.title ?? + key; + if (definitionInSchema) { const backstageReviewOptions = definitionInSchema['ui:backstage']?.review; if (backstageReviewOptions) { if (backstageReviewOptions.mask) { - return [[getLastKey(key), backstageReviewOptions.mask]]; + return [[name, backstageReviewOptions.mask]]; } if (backstageReviewOptions.show === false) { return []; } } - if (definitionInSchema['ui:widget'] === 'password') { - return [[getLastKey(key), '******']]; + if ( + definitionInSchema['ui:widget'] === 'password' || + definitionInSchema['ui:field']?.toLocaleLowerCase('en-us') === 'secret' + ) { + return [[name, '******']]; } if (definitionInSchema.enum && definitionInSchema.enumNames) { return [ [ - getLastKey(key), + name, definitionInSchema.enumNames[ definitionInSchema.enum.indexOf(value) ] || value, @@ -75,7 +83,7 @@ function processSchema( } } - return [[getLastKey(key), value]]; + return [[name, value]]; } /** @@ -93,5 +101,8 @@ export const ReviewState = (props: ReviewStateProps) => { }) .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 index 649d0c3160..bc8001baea 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { isJsonObject, getLastKey } from './util'; +import { isJsonObject, formatKey } from './util'; describe('isJsonObject', () => { it('should return true for non-null objects', () => { @@ -39,40 +39,42 @@ describe('isJsonObject', () => { }); }); -describe('getLastKey', () => { - it('should return the last part of a simple key', () => { - expect(getLastKey('simple')).toBe('simple'); +describe('formatKey', () => { + it('should replace / with > globally in the key', () => { + expect(formatKey('simple/key')).toBe('Simple > Key'); }); - it('should return the last part of a nested key', () => { - expect(getLastKey('parent/child')).toBe('child'); + it('should leave a top-level key untouched', () => { + expect(formatKey('topLevel')).toBe('Top Level'); }); - it('should return the last part of a deeply nested key', () => { - expect(getLastKey('grandparent/parent/child')).toBe('child'); + it('should handle keys with a leading slash', () => { + expect(formatKey('/simple/key')).toBe('Simple > Key'); }); it('should handle keys with trailing slash', () => { - expect(getLastKey('parent/child/')).toBe(''); + expect(formatKey('parent/child/')).toBe('Parent > Child'); }); it('should handle empty string', () => { - expect(getLastKey('')).toBe(''); + expect(formatKey('')).toBe(''); }); it('should handle keys with multiple consecutive slashes', () => { - expect(getLastKey('parent//child')).toBe('child'); + expect(formatKey('parent//child')).toBe('Parent > Child'); }); it('should handle keys with only slashes', () => { - expect(getLastKey('////')).toBe(''); + expect(formatKey('////')).toBe(''); }); it('should handle keys with spaces', () => { - expect(getLastKey('parent/child with spaces')).toBe('child with spaces'); + expect(formatKey('parent/child with spaces')).toBe( + 'Parent > Child With Spaces', + ); }); - it('should handle keys with special characters', () => { - expect(getLastKey('parent/child@!#$%^&*()')).toBe('child@!#$%^&*()'); + 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 index d2caf71964..44311ee206 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/util.ts +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.ts @@ -15,13 +15,17 @@ */ 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 get the last part of the key -export function getLastKey(key: string): string { +// Helper function to format a key into a human-readable string +export function formatKey(key: string): string { const parts = key.split('/'); - return parts[parts.length - 1]; + 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/TemplateCard/CardHeader.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx index 90157c3226..58033b718c 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx @@ -119,7 +119,7 @@ describe('CardHeader', () => { , ); - const favorite = getByRole('button', { name: 'favorite' }); + const favorite = getByRole('button', { name: 'Add to favorites' }); await fireEvent.click(favorite); diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 3c4b39541c..d3080d6edc 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/plugin-scaffolder +## 1.25.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @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.25.0-next.0 + +### Minor Changes + +- 5143616: Added EntityOwnerPicker component to the TemplateListPage to allow filtering on owner + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @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.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 + +## 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 + ## 1.24.0-next.2 ### Minor Changes diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index de0fc463a9..555ab2ac13 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -3,11 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PathParams } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; @@ -16,9 +21,10 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; selectedTemplate: SubRouteRef< @@ -37,7 +43,64 @@ const _default: BackstagePlugin< namespace: string; }>; }, - {} + { + 'api:scaffolder': ExtensionDefinition<{ + kind: 'api'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + }>; + 'page:scaffolder': ExtensionDefinition<{ + kind: 'page'; + namespace: undefined; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + }>; + 'nav-item:scaffolder': ExtensionDefinition<{ + kind: 'nav-item'; + namespace: undefined; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + }>; + } >; export default _default; @@ -47,6 +110,137 @@ export type FormProps = Pick< 'transformErrors' | 'noHtml5Validate' >; +// @alpha (undocumented) +export const scaffolderTranslationRef: TranslationRef< + 'scaffolder', + { + readonly 'fields.entityNamePicker.title': 'Name'; + readonly 'fields.entityNamePicker.description': 'Unique name of the component'; + readonly 'fields.entityPicker.title': 'Entity'; + readonly 'fields.entityPicker.description': 'An entity from the catalog'; + readonly 'fields.entityTagsPicker.title': 'Tags'; + readonly 'fields.entityTagsPicker.description': "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"; + readonly 'fields.myGroupsPicker.title': 'Entity'; + readonly 'fields.myGroupsPicker.description': 'An entity from the catalog'; + readonly 'fields.ownedEntityPicker.title': 'Entity'; + readonly 'fields.ownedEntityPicker.description': 'An entity from the catalog'; + readonly 'fields.ownerPicker.title': 'Owner'; + readonly 'fields.ownerPicker.description': 'The owner of the component'; + readonly 'fields.azureRepoPicker.organization.title': 'Organization'; + readonly 'fields.azureRepoPicker.organization.description': 'The Organization that this repo will belong to'; + readonly 'fields.azureRepoPicker.project.title': 'Project'; + readonly 'fields.azureRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.title': 'Allowed Projects'; + readonly 'fields.bitbucketRepoPicker.project.description': 'The Project that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.project.inputTitle': 'Projects'; + readonly 'fields.bitbucketRepoPicker.workspaces.title': 'Allowed Workspaces'; + readonly 'fields.bitbucketRepoPicker.workspaces.description': 'The Workspace that this repo will belong to'; + readonly 'fields.bitbucketRepoPicker.workspaces.inputTitle': 'Workspaces'; + readonly 'fields.gerritRepoPicker.parent.title': 'Parent'; + readonly 'fields.gerritRepoPicker.parent.description': 'The project parent that the repo will belong to'; + readonly 'fields.gerritRepoPicker.owner.title': 'Owner'; + readonly 'fields.gerritRepoPicker.owner.description': 'The owner of the project (optional)'; + readonly 'fields.giteaRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.giteaRepoPicker.owner.description': 'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.giteaRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.githubRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.githubRepoPicker.owner.description': 'The organization, user or project that this repo will belong to'; + readonly 'fields.githubRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.gitlabRepoPicker.owner.title': 'Owner Available'; + readonly 'fields.gitlabRepoPicker.owner.description': 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.'; + readonly 'fields.gitlabRepoPicker.owner.inputTitle': 'Owner'; + readonly 'fields.repoUrlPicker.host.title': 'Host'; + readonly 'fields.repoUrlPicker.host.description': 'The host where the repository will be created'; + readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; + readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; + readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'actionsPage.content.emptyState.title': 'No information to display'; + readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; + readonly 'actionsPage.content.tableCell.type': 'Type'; + readonly 'actionsPage.content.tableCell.name': 'Name'; + readonly 'actionsPage.content.tableCell.title': 'Title'; + readonly 'actionsPage.content.tableCell.description': 'Description'; + readonly 'actionsPage.content.noRowsDescription': 'No schema defined'; + readonly 'actionsPage.title': 'Installed actions'; + readonly 'actionsPage.action.input': 'Input'; + readonly 'actionsPage.action.output': 'Output'; + readonly 'actionsPage.action.examples': 'Examples'; + readonly 'actionsPage.subtitle': 'This is the collection of all installed actions'; + readonly 'actionsPage.pageTitle': 'Create a New Component'; + readonly 'listTaskPage.content.emptyState.title': 'No information to display'; + readonly 'listTaskPage.content.emptyState.description': 'There are no tasks or there was an issue communicating with backend.'; + readonly 'listTaskPage.content.tableCell.template': 'Template'; + readonly 'listTaskPage.content.tableCell.status': 'Status'; + readonly 'listTaskPage.content.tableCell.owner': 'Owner'; + readonly 'listTaskPage.content.tableCell.created': 'Created'; + readonly 'listTaskPage.content.tableCell.taskID': 'Task ID'; + readonly 'listTaskPage.content.tableTitle': 'Tasks'; + readonly 'listTaskPage.title': 'List template tasks'; + readonly 'listTaskPage.subtitle': 'All tasks that have been started'; + readonly 'listTaskPage.pageTitle': 'Templates Tasks'; + readonly 'ownerListPicker.title': 'Task Owner'; + readonly 'ownerListPicker.options.all': 'All'; + readonly 'ownerListPicker.options.owned': 'Owned'; + readonly 'ongoingTask.title': 'Run of'; + readonly 'ongoingTask.contextMenu.cancel': 'Cancel'; + readonly 'ongoingTask.contextMenu.startOver': 'Start Over'; + readonly 'ongoingTask.contextMenu.hideLogs': 'Hide Logs'; + readonly 'ongoingTask.contextMenu.showLogs': 'Show Logs'; + readonly 'ongoingTask.contextMenu.hideButtonBar': 'Hide Button Bar'; + readonly 'ongoingTask.contextMenu.showButtonBar': 'Show Button Bar'; + readonly 'ongoingTask.subtitle': 'Task {{taskId}}'; + readonly 'ongoingTask.pageTitle.hasTemplateName': 'Run of {{templateName}}'; + readonly 'ongoingTask.pageTitle.noTemplateName': 'Scaffolder Run'; + readonly 'ongoingTask.cancelButtonTitle': 'Cancel'; + readonly 'ongoingTask.startOverButtonTitle': 'Start Over'; + readonly 'ongoingTask.hideLogsButtonTitle': 'Hide Logs'; + readonly 'ongoingTask.showLogsButtonTitle': 'Show Logs'; + readonly 'templateTypePicker.title': 'Categories'; + readonly 'templateEditorPage.title': 'Template Editor'; + readonly 'templateEditorPage.subtitle': 'Edit, preview, and try out templates and template forms'; + readonly 'templateEditorPage.dryRunResults.title': 'Dry-run results'; + readonly 'templateEditorPage.dryRunResultsList.title': 'Result {{resultId}}'; + readonly 'templateEditorPage.dryRunResultsList.deleteButtonTitle': 'Delete result'; + readonly 'templateEditorPage.dryRunResultsList.downloadButtonTitle': 'Download as .zip'; + readonly 'templateEditorPage.dryRunResultsView.tab.output': 'Output'; + readonly 'templateEditorPage.dryRunResultsView.tab.log': 'Log'; + readonly 'templateEditorPage.dryRunResultsView.tab.files': 'Files'; + readonly 'templateEditorPage.taskStatusStepper.skippedStepTitle': 'Skipped'; + readonly 'templateEditorPage.customFieldExplorer.selectFieldLabel': 'Choose Custom Field Extension'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.title': 'Field Options'; + readonly 'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle': 'Apply'; + readonly 'templateEditorPage.customFieldExplorer.preview.title': 'Example Template Spec'; + readonly 'templateEditorPage.templateEditorBrowser.closeConfirmMessage': 'Are you sure? Unsaved changes will be lost'; + readonly 'templateEditorPage.templateEditorBrowser.saveIconTooltip': 'Save all files'; + readonly 'templateEditorPage.templateEditorBrowser.reloadIconTooltip': 'Reload directory'; + readonly 'templateEditorPage.templateEditorBrowser.closeIconTooltip': 'Close directory'; + readonly 'templateEditorPage.templateEditorIntro.title': 'Get started by choosing one of the options below'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.title': 'Load Template Directory'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.description': 'Load a local template directory, allowing you to both edit and try executing your own template.'; + readonly 'templateEditorPage.templateEditorIntro.loadLocal.unsupportedTooltip': 'Only supported in some Chromium-based browsers'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.title': 'Edit Template Form'; + readonly 'templateEditorPage.templateEditorIntro.formEditor.description': 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.title': 'Custom Field Explorer'; + readonly 'templateEditorPage.templateEditorIntro.fieldExplorer.description': 'View and play around with available installed custom field extensions.'; + readonly 'templateEditorPage.templateEditorTextArea.saveIconTooltip': 'Save file'; + readonly 'templateEditorPage.templateEditorTextArea.refreshIconTooltip': 'Reload file'; + readonly 'templateEditorPage.templateFormPreviewer.title': 'Load Existing Template'; + readonly 'templateListPage.title': 'Create a new component'; + readonly 'templateListPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateListPage.pageTitle': 'Create a new component'; + readonly 'templateListPage.templateGroups.defaultTitle': 'Templates'; + readonly 'templateListPage.templateGroups.otherTitle': 'Other Templates'; + readonly 'templateListPage.contentHeader.title': 'Available Templates'; + readonly 'templateListPage.contentHeader.registerExistingButtonTitle': 'Register Existing Component'; + readonly 'templateListPage.contentHeader.supportButtonTitle': 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).'; + readonly 'templateListPage.additionalLinksForEntity.viewTechDocsTitle': 'View TechDocs'; + readonly 'templateWizardPage.title': 'Create a new component'; + readonly 'templateWizardPage.subtitle': 'Create new software components using standard templates in your organization'; + readonly 'templateWizardPage.pageTitle': 'Create a new component'; + readonly 'templateWizardPage.pageContextMenu.editConfigurationTitle': 'Edit Configuration'; + } +>; + // @alpha (undocumented) export type TemplateListPageProps = { TemplateCardComponent?: React_2.ComponentType<{ diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 43b94a9361..2e75d776d2 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.24.0-next.2", + "version": "1.25.0-next.1", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx index 37e2978802..302dddb800 100644 --- a/plugins/scaffolder/src/alpha.tsx +++ b/plugins/scaffolder/src/alpha.tsx @@ -16,14 +16,14 @@ import React from 'react'; import { - createApiExtension, createApiFactory, - createNavItemExtension, - createPageExtension, - createPlugin, + createFrontendPlugin, discoveryApiRef, fetchApiRef, identityApiRef, + ApiBlueprint, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import { @@ -51,40 +51,48 @@ export { type TemplateWizardPageProps, } from './next'; -const scaffolderApi = createApiExtension({ - factory: createApiFactory({ - api: scaffolderApiRef, - deps: { - discoveryApi: discoveryApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - fetchApi: fetchApiRef, - identityApi: identityApiRef, - }, - factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => - new ScaffolderClient({ - discoveryApi, - scmIntegrationsApi, - fetchApi, - identityApi, - }), - }), +export { scaffolderTranslationRef } from './translation'; + +const scaffolderApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => + new ScaffolderClient({ + discoveryApi, + scmIntegrationsApi, + fetchApi, + identityApi, + }), + }), + }, }); -const scaffolderPage = createPageExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - defaultPath: '/create', - loader: () => - import('./components/Router').then(m => compatWrapper()), +const scaffolderPage = PageBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + defaultPath: '/create', + loader: () => + import('./components/Router').then(m => compatWrapper()), + }, }); -const scaffolderNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - title: 'Create...', - icon: CreateComponentIcon, +const scaffolderNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Create...', + icon: CreateComponentIcon, + }, }); /** @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'scaffolder', routes: convertLegacyRouteRefs({ root: rootRouteRef, diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 7e859e272d..2f9bcba279 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -58,6 +58,8 @@ import { rootRouteRef, scaffolderListTaskRouteRef, } from '../../routes'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ code: { @@ -115,6 +117,7 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { const ActionPageContent = () => { const api = useApi(scaffolderApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const classes = useStyles(); const { loading, value, error } = useAsync(async () => { @@ -132,8 +135,8 @@ const ActionPageContent = () => { ); @@ -141,17 +144,21 @@ const ActionPageContent = () => { const renderTable = (rows?: JSX.Element[]) => { if (!rows || rows.length < 1) { - return No schema defined; + return ( + {t('actionsPage.content.noRowsDescription')} + ); } return (
    - Name - Title - Description - Type + {t('actionsPage.content.tableCell.name')} + {t('actionsPage.content.tableCell.title')} + + {t('actionsPage.content.tableCell.description')} + + {t('actionsPage.content.tableCell.type')} {rows} @@ -295,7 +302,7 @@ const ActionPageContent = () => { {action.schema?.input && ( - Input + {t('actionsPage.action.input')} {renderTable( formatRows(`${action.id}.input`, action?.schema?.input), @@ -306,7 +313,7 @@ const ActionPageContent = () => { {action.schema?.output && ( - Output + {t('actionsPage.action.output')} {renderTable( formatRows(`${action.id}.output`, action?.schema?.output), @@ -317,7 +324,7 @@ const ActionPageContent = () => { }> - Examples + {t('actionsPage.action.examples')} @@ -336,6 +343,7 @@ export const ActionsPage = () => { const editorLink = useRouteRef(editRouteRef); const tasksLink = useRouteRef(scaffolderListTaskRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -347,9 +355,9 @@ export const ActionsPage = () => { return (
    diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index d37716a710..7bc34662ef 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -41,6 +41,8 @@ import { import { actionsRouteRef, editRouteRef, rootRouteRef } from '../../routes'; import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; import { useNavigate } from 'react-router-dom'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; export interface MyTaskPageProps { initiallySelectedFilter?: 'owned' | 'all'; @@ -48,6 +50,7 @@ export interface MyTaskPageProps { const ListTaskPageContent = (props: MyTaskPageProps) => { const { initiallySelectedFilter = 'owned' } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); const rootLink = useRouteRef(rootRouteRef); @@ -76,8 +79,8 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { ); @@ -94,17 +97,17 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { data={value?.tasks ?? []} - title="Tasks" + title={t('listTaskPage.content.tableTitle')} columns={[ { - title: 'Task ID', + title: t('listTaskPage.content.tableCell.taskID'), field: 'id', render: row => ( {row.id} ), }, { - title: 'Template', + title: t('listTaskPage.content.tableCell.template'), field: 'spec.templateInfo.entity.metadata.title', render: row => ( { ), }, { - title: 'Created', + title: t('listTaskPage.content.tableCell.created'), field: 'createdAt', render: row => , }, { - title: 'Owner', + title: t('listTaskPage.content.tableCell.owner'), field: 'createdBy', render: row => ( ), }, { - title: 'Status', + title: t('listTaskPage.content.tableCell.status'), field: 'status', render: row => , }, @@ -141,6 +144,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => { const editorLink = useRouteRef(editRouteRef); const actionsLink = useRouteRef(actionsRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -151,9 +155,9 @@ export const ListTasksPage = (props: MyTaskPageProps) => { return (
    diff --git a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx index d2489303d3..00efed45ba 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx @@ -23,6 +23,11 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import SettingsIcon from '@material-ui/icons/Settings'; import React, { Fragment } from 'react'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; import AllIcon from '@material-ui/icons/FontDownload'; @@ -64,19 +69,21 @@ export type ButtonGroup = { }[]; }; -function getFilterGroups(): ButtonGroup[] { +function getFilterGroups( + t: TranslationFunction, +): ButtonGroup[] { return [ { - name: 'Task Owner', + name: t('ownerListPicker.title'), items: [ { id: 'owned', - label: 'Owned', + label: t('ownerListPicker.options.owned'), icon: SettingsIcon, }, { id: 'all', - label: 'All', + label: t('ownerListPicker.options.all'), icon: AllIcon, }, ], @@ -90,8 +97,9 @@ export const OwnerListPicker = (props: { }) => { const { filter, onSelectOwner } = props; const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); - const filterGroups = getFilterGroups(); + const filterGroups = getFilterGroups(t); return ( {filterGroups.map(group => ( diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index f96f83fd2f..e0ab01fc67 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -36,6 +36,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; type ContextMenuProps = { cancelEnabled?: boolean; @@ -69,6 +71,7 @@ export const ContextMenu = (props: ContextMenuProps) => { const scaffolderApi = useApi(scaffolderApiRef); const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState(); + const { t } = useTranslationRef(scaffolderTranslationRef); const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { if (taskId) { @@ -118,14 +121,24 @@ export const ContextMenu = (props: ContextMenuProps) => { - + onToggleButtonBar?.(!buttonBarVisible)}> { - + { - + diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 459ba682d2..b956e27aca 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -42,6 +42,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -85,6 +87,7 @@ export const OngoingTask = (props: { })) ?? [], [taskStream], ); + const { t } = useTranslationRef(scaffolderTranslationRef); const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); @@ -166,7 +169,7 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const templateName = - taskStream.task?.spec.templateInfo?.entity?.metadata.name; + taskStream.task?.spec.templateInfo?.entity?.metadata.name || ''; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); @@ -174,14 +177,16 @@ export const OngoingTask = (props: {
    - Run of {templateName} + {t('ongoingTask.title')} {templateName} } - subtitle={`Task ${taskId}`} + subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })} > - Cancel + {t('ongoingTask.cancelButtonTitle')} diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index aa6630d4aa..4c4b88e471 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-catalog-react'; import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ @@ -86,7 +86,7 @@ const apis = TestApiRegistry.from( describe('', () => { it('renders available entity types', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ', () => { }); it('sets the selected type filters', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ; const checkedIcon = ; @@ -41,6 +43,7 @@ export const TemplateTypePicker = () => { const alertApi = useApi(alertApiRef); const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter(); + const { t } = useTranslationRef(scaffolderTranslationRef); if (loading) return ; @@ -61,7 +64,7 @@ export const TemplateTypePicker = () => { component="label" htmlFor="categories-picker" > - Categories + {t('templateTypePicker.title')} id="categories-picker" diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 9f49fc2b94..c219ae0d63 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityNamePickerProps } from './schema'; import TextField from '@material-ui/core/TextField'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityNamePickerSchema } from './schema'; @@ -23,10 +25,14 @@ export { EntityNamePickerSchema } from './schema'; * EntityName Picker */ export const EntityNamePicker = (props: EntityNamePickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, required, - schema: { title = 'Name', description = 'Unique name of the component' }, + schema: { + title = t('fields.entityNamePicker.title'), + description = t('fields.entityNamePicker.description'), + }, rawErrors, formData, uiSchema: { 'ui:autofocus': autoFocus }, diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 9eca3e85c5..b97b776b19 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -44,6 +44,8 @@ import { EntityPickerFilterQuery, } from './schema'; import { VirtualizedListbox } from '../VirtualizedListbox'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityPickerSchema } from './schema'; @@ -54,9 +56,13 @@ export { EntityPickerSchema } from './schema'; * @public */ export const EntityPicker = (props: EntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.entityPicker.title'), + description = t('fields.entityPicker.description'), + }, required, uiSchema, rawErrors, diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index bf95a08c85..25b5d926a5 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -24,6 +24,8 @@ import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { EntityTagsPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityTagsPickerSchema } from './schema'; @@ -43,6 +45,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const kinds = uiSchema['ui:options']?.kinds; const showCounts = uiSchema['ui:options']?.showCounts; const helperText = uiSchema['ui:options']?.helperText; + const { t } = useTranslationRef(scaffolderTranslationRef); const { loading, value: existingTags } = useAsync(async () => { const facet = 'metadata.tags'; @@ -109,13 +112,10 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { renderInput={params => ( setInputValue(e.target.value)} error={inputError} - helperText={ - helperText ?? - "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters" - } + helperText={helperText ?? t('fields.entityTagsPicker.description')} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} /> )} diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx index 94261ce0e4..74af578a21 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx @@ -15,11 +15,14 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import { CatalogApi } from '@backstage/catalog-client'; import { MyGroupsPicker } from './MyGroupsPicker'; -import { TestApiProvider } from '@backstage/test-utils'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { ErrorApi, @@ -29,6 +32,7 @@ import { } from '@backstage/core-plugin-api'; import userEvent from '@testing-library/user-event'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; // Create a mock IdentityApi const mockIdentityApi: IdentityApi = { @@ -111,12 +115,16 @@ describe('', () => { required, } as unknown as FieldProps; - render( + await renderInTestApp( @@ -183,12 +191,16 @@ describe('', () => { required, } as unknown as FieldProps; - const { queryByText, getByRole } = render( + const { queryByText, getByRole } = await renderInTestApp( @@ -240,12 +252,16 @@ describe('', () => { required, } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( @@ -300,12 +316,16 @@ describe('', () => { formData: 'group:default/group1', } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx index 1e78fa45a1..50aa42df36 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useState } from 'react'; +import React, { useEffect } from 'react'; import { errorApiRef, identityApiRef, @@ -23,17 +23,31 @@ import { import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; import { MyGroupsPickerProps, MyGroupsPickerSchema } from './schema'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import Autocomplete, { + createFilterOptions, +} from '@material-ui/lab/Autocomplete'; +import { + catalogApiRef, + EntityDisplayName, + entityPresentationApiRef, + EntityRefPresentationSnapshot, +} from '@backstage/plugin-catalog-react'; import { NotFoundError } from '@backstage/errors'; import useAsync from 'react-use/esm/useAsync'; import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { VirtualizedListbox } from '../VirtualizedListbox'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { MyGroupsPickerSchema }; export const MyGroupsPicker = (props: MyGroupsPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title, description }, + schema: { + title = t('fields.myGroupsPicker.title'), + description = t('fields.myGroupsPicker.description'), + }, required, rawErrors, onChange, @@ -43,19 +57,14 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { const identityApi = useApi(identityApiRef); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); - const [groups, setGroups] = useState< - { - label: string; - ref: string; - }[] - >([]); + const entityPresentationApi = useApi(entityPresentationApiRef); - useAsync(async () => { + const { value: groups, loading } = useAsync(async () => { const { userEntityRef } = await identityApi.getBackstageIdentity(); if (!userEntityRef) { errorApi.post(new NotFoundError('No user entity ref found')); - return; + return { catalogEntities: [], entityRefToPresentation: new Map() }; } const { items } = await catalogApi.getEntities({ @@ -65,24 +74,38 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { }, }); - const groupValues = items - .filter((e): e is Entity => Boolean(e)) - .map(item => ({ - label: item.metadata.title ?? item.metadata.name, - ref: stringifyEntityRef(item), - })); + const entityRefToPresentation = new Map< + string, + EntityRefPresentationSnapshot + >( + await Promise.all( + items.map(async item => { + const presentation = await entityPresentationApi.forEntity(item) + .promise; + return [stringifyEntityRef(item), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }), + ), + ); - setGroups(groupValues); + return { catalogEntities: items, entityRefToPresentation }; }); - const updateChange = ( - _: React.ChangeEvent<{}>, - value: { label: string; ref: string } | null, - ) => { - onChange(value?.ref ?? ''); + const updateChange = (_: React.ChangeEvent<{}>, value: Entity | null) => { + onChange(value ? stringifyEntityRef(value) : ''); }; - const selectedEntity = groups?.find(e => e.ref === formData) || null; + const selectedEntity = + groups?.catalogEntities.find(e => stringifyEntityRef(e) === formData) || + null; + + useEffect(() => { + if (groups?.catalogEntities.length === 1 && !selectedEntity) { + onChange(stringifyEntityRef(groups.catalogEntities[0])); + } + }, [groups, onChange, selectedEntity]); return ( { error={rawErrors?.length > 0} > group.label} + getOptionLabel={option => + groups?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle! + } + autoSelect renderInput={params => ( { FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} + InputProps={params.InputProps} /> )} + renderOption={option => } + filterOptions={createFilterOptions({ + stringify: option => + groups?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle!, + })} + ListboxComponent={VirtualizedListbox} /> ); diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 02c437ceb6..06fdf17824 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -23,6 +23,8 @@ import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnedEntityPickerProps } from './schema'; import { EntityPickerProps } from '../EntityPicker/schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnedEntityPickerSchema } from './schema'; @@ -33,8 +35,12 @@ export { OwnedEntityPickerSchema } from './schema'; * @public */ export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.ownedEntityPicker.title'), + description = t('fields.ownedEntityPicker.description'), + }, uiSchema, required, } = props; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 5c69e73e12..792685f040 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnerPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnerPickerSchema } from './schema'; @@ -26,8 +28,12 @@ export { OwnerPickerSchema } from './schema'; * @public */ export const OwnerPicker = (props: OwnerPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Owner', description = 'The owner of the component' }, + schema: { + title = t('fields.ownerPicker.title'), + description = t('fields.ownerPicker.description'), + }, uiSchema, ...restProps } = props; diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx index 9350e232f8..0fecfb7947 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.tsx @@ -17,8 +17,7 @@ import FormControl from '@material-ui/core/FormControl'; import React from 'react'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { BaseRepoBranchPickerProps } from './types'; @@ -43,9 +42,9 @@ export const DefaultRepoBranchPicker = ({ required={required} error={rawErrors?.length > 0 && !branch} > - Branch - onChange({ branch: e.target.value })} value={branch} /> diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx index 0380ac6ce8..904fb615eb 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx @@ -16,11 +16,12 @@ import React from 'react'; import { AzureRepoPicker } from './AzureRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('AzureRepoPicker', () => { it('renders the two input fields', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -30,9 +31,9 @@ describe('AzureRepoPicker', () => { }); describe('org field', () => { - it('calls onChange when the organisation changes', () => { + it('calls onChange when the organisation changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -45,9 +46,9 @@ describe('AzureRepoPicker', () => { }); describe('project field', () => { - it('calls onChange when the project changes', () => { + it('calls onChange when the project changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index 48fcb4f6af..59e666482a 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -17,10 +17,11 @@ import React from 'react'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; import { Select, SelectItem } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export const AzureRepoPicker = ( props: BaseRepoUrlPickerProps<{ @@ -35,6 +36,7 @@ export const AzureRepoPicker = ( state, onChange, } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const organizationItems: SelectItem[] = allowedOrganizations ? allowedOrganizations.map(i => ({ label: i, value: i })) @@ -54,29 +56,30 @@ export const AzureRepoPicker = ( error={rawErrors?.length > 0 && !organization} > {allowedOrganizations?.length ? ( - onChange({ organization: e.target.value })} - value={organization} + - onChange({ project: String(Array.isArray(s) ? s[0] : s) }) - } - disabled={allowedProject.length === 1} - selected={project} - items={projectItems} - /> - ) : ( <> - Project - onChange({ project: e.target.value })} - value={project} + onChange({ workspace: String(Array.isArray(s) ? s[0] : s) }) } @@ -180,14 +184,18 @@ export const BitbucketRepoPicker = ( }} options={availableWorkspaces} renderInput={params => ( - + )} freeSolo autoSelect /> )} - The Workspace that this repo will belong to + {t('fields.bitbucketRepoPicker.workspaces.description')} )} @@ -199,7 +207,7 @@ export const BitbucketRepoPicker = ( {allowedProjects?.length ? ( onChange({ owner: e.target.value })} + helperText={t('fields.gerritRepoPicker.owner.description')} value={owner} /> - The owner of the project (optional) 0 && !workspace} > - Parent - onChange({ workspace: e.target.value })} value={workspace} + helperText={t('fields.gerritRepoPicker.parent.description')} /> - - The project parent that the repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx index e66ca49af1..056d8f0d11 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GiteaRepoPicker } from './GiteaRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GiteaRepoPicker', () => { describe('owner input field', () => { - it('calls onChange when the owner input changes', () => { + it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], state, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -42,32 +44,36 @@ export const GiteaRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ + owner: String( + Array.isArray(selected) ? selected[0] : selected, + ), + }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.giteaRepoPicker.owner.description')} + + ) : ( <> - Owner - onChange({ owner: e.target.value })} + helperText={t('fields.giteaRepoPicker.owner.description')} value={owner} /> )} - - Gitea namespace where this repository will belong to. It can be the - name of organization, group, subgroup, user, or the project. - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx index 63bc5b8013..3a01c6de7c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GithubRepoPicker } from './GithubRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GithubRepoPicker', () => { describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('calls onChange when the owner is changed to a different owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1', 'owner2']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); }); - it('is disabled picked when only one allowed owner', () => { + it('is disabled picked when only one allowed owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should display free text if no allowed owners are passed', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], rawErrors, state, onChange } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -41,29 +43,30 @@ export const GithubRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - onChange({ owner: e.target.value })} - value={owner} + - onChange({ - owner: String(Array.isArray(selected) ? selected[0] : selected), - }) - } - disabled={allowedOwners.length === 1} - selected={owner} - items={ownerItems} - /> - ) : ( <> - Owner - onChange({ owner: e.target.value })} - value={owner} + onChange(String(Array.isArray(s) ? s[0] : s))} selected={host} items={hostsOptions} @@ -81,7 +84,7 @@ export const RepoUrlPickerHost = (props: { /> - The host where the repository will be created + {t('fields.repoUrlPicker.host.description')} diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.test.tsx index 95407084c6..ec4853c714 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.test.tsx @@ -15,15 +15,16 @@ */ import React from 'react'; import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { act } from 'react-dom/test-utils'; +import { renderInTestApp } from '@backstage/test-utils'; describe('RepoUrlPickerRepoName', () => { it('should call onChange with the first allowed repo if there is none set already', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( { const onChange = jest.fn(); - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should render a normal text area when no options are passed', async () => { const onChange = jest.fn(); - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { const onChange = jest.fn(); - const { getByRole, getByText } = render( + const { getByRole, getByText } = await renderInTestApp( { const { repoName, allowedRepos, onChange, rawErrors, availableRepos } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); useEffect(() => { // If there is no repoName chosen currently @@ -53,7 +56,7 @@ export const RepoUrlPickerRepoName = (props: { {allowedRepos?.length ? ( handleSelectionChange(e.target.value as FieldExtensionOptions) @@ -144,7 +147,9 @@ export const CustomFieldExplorer = ({
    - +
    - Apply + {t( + 'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle', + )}
    @@ -173,7 +180,9 @@ export const CustomFieldExplorer = ({
    - + ({ header: { @@ -51,6 +53,7 @@ export function DryRunResults() { const dryRun = useDryRun(); const [expanded, setExpanded] = useState(false); const [hidden, setHidden] = useState(true); + const { t } = useTranslationRef(scaffolderTranslationRef); const resultsLength = dryRun.results.length; const prevResultsLength = usePrevious(resultsLength); @@ -76,7 +79,7 @@ export function DryRunResults() { className={classes.header} expandIcon={} > - Dry-run results + {t('templateEditorPage.dryRunResults.title')} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx index 38cc3f1762..9f44933466 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx @@ -28,6 +28,8 @@ import DownloadIcon from '@material-ui/icons/GetApp'; import React from 'react'; import { useDryRun } from '../DryRunContext'; import { downloadBlob } from '../../../lib/download'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -49,6 +51,7 @@ const useStyles = makeStyles(theme => ({ export function DryRunResultsList() { const classes = useStyles(); const dryRun = useDryRun(); + const { t } = useTranslationRef(scaffolderTranslationRef); return ( @@ -77,12 +80,18 @@ export function DryRunResultsList() { > {failed ? : } - + downloadResult()} > @@ -91,7 +100,9 @@ export function DryRunResultsList() { dryRun.deleteResult(result.id)} > diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx index fb69dabe13..7de248dd56 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx @@ -29,6 +29,8 @@ import { DryRunResultsSplitView } from './DryRunResultsSplitView'; import { FileBrowser } from '../../../components/FileBrowser'; import { TaskPageLinks } from './TaskPageLinks'; import { TaskStatusStepper } from './TaskStatusStepper'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; const useStyles = makeStyles({ root: { @@ -173,13 +175,23 @@ export function DryRunResultsView() { const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( 'files', ); + const { t } = useTranslationRef(scaffolderTranslationRef); return (
    setSelectedTab(v)}> - - - + + + diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/TaskStatusStepper.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/TaskStatusStepper.tsx index cde3d6cfb0..7696051950 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/TaskStatusStepper.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/TaskStatusStepper.tsx @@ -31,6 +31,8 @@ import { DateTime, Interval } from 'luxon'; import useInterval from 'react-use/esm/useInterval'; import humanizeDuration from 'humanize-duration'; import classNames from 'classnames'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -148,6 +150,7 @@ export const TaskStatusStepper = memo( }) => { const { steps, currentStepId, onUserStepChange } = props; const classes = useStyles(props); + const { t } = useTranslationRef(scaffolderTranslationRef); return (
    @@ -178,7 +181,11 @@ export const TaskStatusStepper = memo(
    {step.name} {isSkipped ? ( - Skipped + + {t( + 'templateEditorPage.taskStatusStepper.skippedStepTitle', + )} + ) : ( )} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx index 489ec05bbe..8bea86b074 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx @@ -23,6 +23,8 @@ import SaveIcon from '@material-ui/icons/Save'; import React from 'react'; import { useDirectoryEditor } from './DirectoryEditorContext'; import { FileBrowser } from '../../components/FileBrowser'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ button: { @@ -47,6 +49,7 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) { const classes = useStyles(); const directoryEditor = useDirectoryEditor(); const changedFiles = directoryEditor.files.filter(file => file.dirty); + const { t } = useTranslationRef(scaffolderTranslationRef); const handleClose = () => { if (!props.onClose) { @@ -55,7 +58,7 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) { if (changedFiles.length > 0) { // eslint-disable-next-line no-alert const accepted = window.confirm( - 'Are you sure? Unsaved changes will be lost', + t('templateEditorPage.templateEditorBrowser.closeConfirmMessage'), ); if (!accepted) { return; @@ -67,7 +70,9 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) { return ( <>
    - + !file.dirty)} @@ -76,7 +81,11 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) { - + directoryEditor.reload()} @@ -85,7 +94,9 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
    - + diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx index 2a75c1bf2c..d785708a51 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx @@ -23,6 +23,8 @@ import Typography from '@material-ui/core/Typography'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; import { makeStyles } from '@material-ui/core/styles'; import { WebFileSystemAccess } from '../../lib/filesystem'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ introText: { @@ -50,6 +52,7 @@ interface EditorIntroProps { export function TemplateEditorIntro(props: EditorIntroProps) { const classes = useStyles(); const supportsLoad = WebFileSystemAccess.isSupported(); + const { t } = useTranslationRef(scaffolderTranslationRef); const cardLoadLocal = ( @@ -65,14 +68,13 @@ export function TemplateEditorIntro(props: EditorIntroProps) { color={supportsLoad ? undefined : 'textSecondary'} style={{ display: 'flex', flexFlow: 'row nowrap' }} > - Load Template Directory + {t('templateEditorPage.templateEditorIntro.loadLocal.title')} - Load a local template directory, allowing you to both edit and try - executing your own template. + {t('templateEditorPage.templateEditorIntro.loadLocal.description')} @@ -80,7 +82,9 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
    @@ -94,11 +98,10 @@ export function TemplateEditorIntro(props: EditorIntroProps) { props.onSelect?.('form')}> - Edit Template Form + {t('templateEditorPage.templateEditorIntro.formEditor.title')} - Preview and edit a template form, either using a sample template or - by loading a template from the catalog. + {t('templateEditorPage.templateEditorIntro.formEditor.description')} @@ -110,11 +113,12 @@ export function TemplateEditorIntro(props: EditorIntroProps) { props.onSelect?.('field-explorer')}> - Custom Field Explorer + {t('templateEditorPage.templateEditorIntro.fieldExplorer.title')} - View and play around with available installed custom field - extensions. + {t( + 'templateEditorPage.templateEditorIntro.fieldExplorer.description', + )} @@ -124,7 +128,7 @@ export function TemplateEditorIntro(props: EditorIntroProps) { return (
    - Get started by choosing one of the options below + {t('templateEditorPage.templateEditorIntro.title')}
    diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx index ce8db485ea..696b21d1df 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx @@ -27,6 +27,8 @@ import { useKeyboardEvent } from '@react-hookz/web'; import CodeMirror from '@uiw/react-codemirror'; import React, { useMemo } from 'react'; import { useDirectoryEditor } from './DirectoryEditorContext'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ container: { @@ -66,6 +68,7 @@ export function TemplateEditorTextArea(props: { }) { const { errorText } = props; const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); const panelExtension = useMemo(() => { if (!errorText) { @@ -102,7 +105,11 @@ export function TemplateEditorTextArea(props: {
    {props.onSave && ( - + props.onSave?.()} @@ -112,7 +119,11 @@ export function TemplateEditorTextArea(props: { )} {props.onReload && ( - + props.onReload?.()} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx index 7892adfb3f..07f2fd7f07 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -36,6 +36,8 @@ import { } from '@backstage/plugin-scaffolder-react'; import { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: @@ -119,6 +121,7 @@ export const TemplateFormPreviewer = ({ layouts?: LayoutOptions[]; }) => { const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); const [selectedTemplate, setSelectedTemplate] = useState(''); @@ -176,11 +179,11 @@ export const TemplateFormPreviewer = ({
    - Load Existing Template + {t('templateEditorPage.templateFormPreviewer.title')}