diff --git a/.changeset/afraid-items-drum.md b/.changeset/afraid-items-drum.md
deleted file mode 100644
index 94ddd608cd..0000000000
--- a/.changeset/afraid-items-drum.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli-node': patch
----
-
-Updated to use new utilities from `@backstage/cli-common`.
diff --git a/.changeset/alert-api-replay.md b/.changeset/alert-api-replay.md
deleted file mode 100644
index b41ec091a3..0000000000
--- a/.changeset/alert-api-replay.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core-app-api': patch
----
-
-Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription.
diff --git a/.changeset/all-socks-taste.md b/.changeset/all-socks-taste.md
deleted file mode 100644
index 377782c2f9..0000000000
--- a/.changeset/all-socks-taste.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend-module-gitlab': minor
----
-
-In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
diff --git a/.changeset/better-ghosts-tell.md b/.changeset/better-ghosts-tell.md
new file mode 100644
index 0000000000..af66b4fc6e
--- /dev/null
+++ b/.changeset/better-ghosts-tell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': patch
+---
+
+Fixes app background color on dark mode.
diff --git a/.changeset/bumpy-planets-go.md b/.changeset/bumpy-planets-go.md
deleted file mode 100644
index 5477711c1a..0000000000
--- a/.changeset/bumpy-planets-go.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed Checkbox indicator showing checkmark color when unchecked.
-
-Affected components: Checkbox
diff --git a/.changeset/chatty-sides-wear.md b/.changeset/chatty-sides-wear.md
deleted file mode 100644
index 33937a1286..0000000000
--- a/.changeset/chatty-sides-wear.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-graph': patch
----
-
-Fixed zoom out description (replace `&` with &)
diff --git a/.changeset/chilly-bikes-rule.md b/.changeset/chilly-bikes-rule.md
deleted file mode 100644
index 894999cf56..0000000000
--- a/.changeset/chilly-bikes-rule.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@techdocs/cli': patch
----
-
-Updated to use new utilities from `@backstage/cli-common`.
diff --git a/.changeset/chilly-hotels-walk.md b/.changeset/chilly-hotels-walk.md
deleted file mode 100644
index e9b1ff66ba..0000000000
--- a/.changeset/chilly-hotels-walk.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-search-backend-node': minor
----
-
-Improving method that search tokenizer breaks apart entity names
diff --git a/.changeset/chilly-waves-relate.md b/.changeset/chilly-waves-relate.md
deleted file mode 100644
index 2e78f9245e..0000000000
--- a/.changeset/chilly-waves-relate.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@backstage/cli': minor
----
-
-Switched the default module resolution to `bundler` and the `module` setting to `ES2020`.
-
-You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages.
-
-You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
-
-A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
diff --git a/.changeset/clean-toys-reply.md b/.changeset/clean-toys-reply.md
deleted file mode 100644
index 66002903ab..0000000000
--- a/.changeset/clean-toys-reply.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-auth-node': patch
----
-
-remove leading dot in auth cookie cleanup call
diff --git a/.changeset/cold-dogs-study.md b/.changeset/cold-dogs-study.md
new file mode 100644
index 0000000000..a27309fc15
--- /dev/null
+++ b/.changeset/cold-dogs-study.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-backend': patch
+---
+
+Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`.
diff --git a/.changeset/common-coins-stare.md b/.changeset/common-coins-stare.md
deleted file mode 100644
index 0bd0078cf9..0000000000
--- a/.changeset/common-coins-stare.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element.
-
-Affected components: ButtonIcon
diff --git a/.changeset/crazy-hornets-spend.md b/.changeset/crazy-hornets-spend.md
deleted file mode 100644
index 4db99675ac..0000000000
--- a/.changeset/crazy-hornets-spend.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@backstage/plugin-catalog-graph': patch
-'@backstage/plugin-api-docs': patch
-'@backstage/plugin-techdocs': patch
-'@backstage/plugin-catalog': patch
-'@backstage/plugin-search': patch
-'@backstage/plugin-app': patch
-'@backstage/plugin-org': patch
----
-
-Minor extension type updates after frontend API bump
diff --git a/.changeset/create-app-1764689798.md b/.changeset/create-app-1766498726.md
similarity index 100%
rename from .changeset/create-app-1764689798.md
rename to .changeset/create-app-1766498726.md
diff --git a/.changeset/dependabot-0d8f6de.md b/.changeset/dependabot-0d8f6de.md
deleted file mode 100644
index b0a3955f2d..0000000000
--- a/.changeset/dependabot-0d8f6de.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-'@backstage/backend-defaults': patch
-'@backstage/backend-dynamic-feature-service': patch
-'@backstage/backend-openapi-utils': patch
-'@backstage/backend-test-utils': patch
-'@backstage/cli': patch
-'@backstage/plugin-app-backend': patch
-'@backstage/plugin-app-node': patch
-'@backstage/plugin-auth-backend-module-atlassian-provider': patch
-'@backstage/plugin-auth-backend-module-auth0-provider': patch
-'@backstage/plugin-auth-backend-module-aws-alb-provider': patch
-'@backstage/plugin-auth-backend-module-azure-easyauth-provider': patch
-'@backstage/plugin-auth-backend-module-bitbucket-provider': patch
-'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch
-'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch
-'@backstage/plugin-auth-backend-module-gitlab-provider': patch
-'@backstage/plugin-auth-backend-module-guest-provider': patch
-'@backstage/plugin-auth-backend-module-microsoft-provider': patch
-'@backstage/plugin-auth-backend-module-oidc-provider': patch
-'@backstage/plugin-auth-backend-module-okta-provider': patch
-'@backstage/plugin-auth-backend-module-onelogin-provider': patch
-'@backstage/plugin-auth-backend-module-openshift-provider': patch
-'@backstage/plugin-auth-backend-module-pinniped-provider': patch
-'@backstage/plugin-auth-backend': patch
-'@backstage/plugin-auth-node': patch
-'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
-'@backstage/plugin-catalog-backend': patch
-'@backstage/plugin-devtools-backend': patch
-'@backstage/plugin-events-backend': patch
-'@backstage/plugin-events-node': patch
-'@backstage/plugin-gateway-backend': patch
-'@backstage/plugin-kubernetes-backend': patch
-'@backstage/plugin-mcp-actions-backend': patch
-'@backstage/plugin-notifications-backend': patch
-'@backstage/plugin-permission-backend': patch
-'@backstage/plugin-permission-node': patch
-'@backstage/plugin-proxy-backend': patch
-'@backstage/plugin-scaffolder-backend': patch
-'@backstage/plugin-search-backend': patch
-'@backstage/plugin-signals-backend': patch
-'@backstage/plugin-signals-node': patch
-'@backstage/plugin-techdocs-backend': patch
-'@backstage/plugin-techdocs-node': patch
-'@backstage/plugin-user-settings-backend': patch
----
-
-chore(deps): bump `express` from 4.21.2 to 4.22.0
diff --git a/.changeset/dependabot-5e654fe.md b/.changeset/dependabot-5e654fe.md
deleted file mode 100644
index 93563cffc6..0000000000
--- a/.changeset/dependabot-5e654fe.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-defaults': patch
----
-
-build(deps): bump `node-forge` from 1.3.1 to 1.3.2
diff --git a/.changeset/dependabot-aed8e01.md b/.changeset/dependabot-aed8e01.md
new file mode 100644
index 0000000000..8606ea8b91
--- /dev/null
+++ b/.changeset/dependabot-aed8e01.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': patch
+---
+
+build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10
diff --git a/.changeset/early-doors-visit.md b/.changeset/early-doors-visit.md
deleted file mode 100644
index 41cd9e92b8..0000000000
--- a/.changeset/early-doors-visit.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-defaults': patch
----
-
-Allow configuration of the `referrerPolicy`
diff --git a/.changeset/error-boundary-api.md b/.changeset/error-boundary-api.md
deleted file mode 100644
index 74803f1dc0..0000000000
--- a/.changeset/error-boundary-api.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/frontend-plugin-api': patch
----
-
-Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries.
-
-The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode.
diff --git a/.changeset/every-bears-add.md b/.changeset/every-bears-add.md
new file mode 100644
index 0000000000..83c6d6b06d
--- /dev/null
+++ b/.changeset/every-bears-add.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Bump `@swc/core` to support `ES2023` and `ES2024`
diff --git a/.changeset/famous-jars-lose.md b/.changeset/famous-jars-lose.md
deleted file mode 100644
index 4070701e4e..0000000000
--- a/.changeset/famous-jars-lose.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link.
-
-Affected components: Row
diff --git a/.changeset/fifty-coats-feel.md b/.changeset/fifty-coats-feel.md
deleted file mode 100644
index 0e3425a505..0000000000
--- a/.changeset/fifty-coats-feel.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-kubernetes-react': patch
----
-
-Enabled a pod terminal at GKE
diff --git a/.changeset/fifty-lights-marry.md b/.changeset/fifty-lights-marry.md
deleted file mode 100644
index 423ef89721..0000000000
--- a/.changeset/fifty-lights-marry.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
-'@backstage/plugin-kubernetes-backend': patch
-'@backstage/backend-defaults': patch
-'@backstage/plugin-scaffolder-react': patch
-'@backstage/integration': patch
----
-
-Updated luxon types
diff --git a/.changeset/fine-eagles-sleep.md b/.changeset/fine-eagles-sleep.md
deleted file mode 100644
index 0af6d0dfc0..0000000000
--- a/.changeset/fine-eagles-sleep.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/backend-app-api': minor
----
-
-Introduced backend startup result tracking and error handling. The `Backend.start()` method now returns a `BackendStartupResult` with detailed success/failure status and timing information for all plugins and modules. When startup fails, a `BackendStartupError` is thrown that includes the complete startup results, making it easier to diagnose which plugins or modules failed.
-
-This also improves the default error message when backend startup fails, and of course makes it possible to craft your own custom error reporting based on the startup results.
diff --git a/.changeset/flat-pillows-rush.md b/.changeset/flat-pillows-rush.md
deleted file mode 100644
index bb64013f62..0000000000
--- a/.changeset/flat-pillows-rush.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/frontend-plugin-api': patch
----
-
-Fixed a versioning conflict that could result in a `.withContext` is not a function error.
diff --git a/.changeset/floppy-bobcats-serve.md b/.changeset/floppy-bobcats-serve.md
deleted file mode 100644
index d698e1482c..0000000000
--- a/.changeset/floppy-bobcats-serve.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-'@backstage/core-components': patch
----
-
-Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection.
-
-In addition, improved the `LogViewer` component by a few usability enhancements:
-
-- Added support for multiple selections using cmd/ctrl + click
-- Improved the generated hash that is added to the URL to also support ranges & multiple selections
-- Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality
-- Added some color and a separator to the line numbers to improve readability
diff --git a/.changeset/four-peaches-train.md b/.changeset/four-peaches-train.md
deleted file mode 100644
index 81b35589c1..0000000000
--- a/.changeset/four-peaches-train.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/backend-defaults': patch
-'@backstage/backend-plugin-api': patch
----
-
-**BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`.
diff --git a/.changeset/fruity-rivers-arrive.md b/.changeset/fruity-rivers-arrive.md
deleted file mode 100644
index a816946bcc..0000000000
--- a/.changeset/fruity-rivers-arrive.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': minor
----
-
-Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity.
diff --git a/.changeset/fruity-words-melt.md b/.changeset/fruity-words-melt.md
deleted file mode 100644
index 0f02acee2b..0000000000
--- a/.changeset/fruity-words-melt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-techdocs-module-addons-contrib': patch
----
-
-Updated tests to match test-utils change
diff --git a/.changeset/full-needles-drive.md b/.changeset/full-needles-drive.md
deleted file mode 100644
index d0d7a59551..0000000000
--- a/.changeset/full-needles-drive.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-kubernetes-backend': minor
-'@backstage/plugin-kubernetes-node': minor
----
-
-Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
diff --git a/.changeset/funny-hornets-peel.md b/.changeset/funny-hornets-peel.md
deleted file mode 100644
index 2af6702295..0000000000
--- a/.changeset/funny-hornets-peel.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Updated `isolated-vm` to `6.0.1`
diff --git a/.changeset/funny-papayas-rest.md b/.changeset/funny-papayas-rest.md
deleted file mode 100644
index 95f52966ec..0000000000
--- a/.changeset/funny-papayas-rest.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/repo-tools': patch
-'@backstage/codemods': patch
----
-
-Updated to use new utilities from `@backstage/cli-common`.
diff --git a/.changeset/fuzzy-phones-own.md b/.changeset/fuzzy-phones-own.md
deleted file mode 100644
index 61725d1f1d..0000000000
--- a/.changeset/fuzzy-phones-own.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-'@backstage/plugin-catalog-backend-module-github-org': patch
-'@backstage/plugin-catalog-backend-module-github': patch
----
-
-Introduce new configuration option to exclude suspended users from GitHub Enterprise instances.
-
-When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances.
-Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail.
diff --git a/.changeset/fuzzy-rivers-travel.md b/.changeset/fuzzy-rivers-travel.md
deleted file mode 100644
index fa2c08a671..0000000000
--- a/.changeset/fuzzy-rivers-travel.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/frontend-plugin-api': patch
----
-
-Made the return type of `.withOverrides` to be simplified.
diff --git a/.changeset/fuzzy-trees-live.md b/.changeset/fuzzy-trees-live.md
deleted file mode 100644
index 84cec56fab..0000000000
--- a/.changeset/fuzzy-trees-live.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core-components': patch
----
-
-long words like urls now breaks to new line on warning panels instead of overflowing the container
diff --git a/.changeset/gentle-singers-love.md b/.changeset/gentle-singers-love.md
deleted file mode 100644
index ed4372ee06..0000000000
--- a/.changeset/gentle-singers-love.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-app': patch
----
-
-Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system
diff --git a/.changeset/gentle-trains-juggle.md b/.changeset/gentle-trains-juggle.md
deleted file mode 100644
index e8b1397180..0000000000
--- a/.changeset/gentle-trains-juggle.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-kubernetes-backend': patch
----
-
-Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`,
-as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme)
diff --git a/.changeset/great-files-shave.md b/.changeset/great-files-shave.md
deleted file mode 100644
index 271a8e8955..0000000000
--- a/.changeset/great-files-shave.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-react': patch
----
-
-Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters.
diff --git a/.changeset/green-lizards-boil.md b/.changeset/green-lizards-boil.md
deleted file mode 100644
index 640699e001..0000000000
--- a/.changeset/green-lizards-boil.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-react': patch
----
-
-Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions.
diff --git a/.changeset/happy-bottles-invite.md b/.changeset/happy-bottles-invite.md
deleted file mode 100644
index 07fc222cad..0000000000
--- a/.changeset/happy-bottles-invite.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-defaults': minor
----
-
-Move `better-sqlite3` from dependencies to peer dependencies
diff --git a/.changeset/happy-streets-dress.md b/.changeset/happy-streets-dress.md
deleted file mode 100644
index 46f4d798ed..0000000000
--- a/.changeset/happy-streets-dress.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/create-app': patch
----
-
-Updated engines to support Node 22 or 24
diff --git a/.changeset/honest-bears-itch.md b/.changeset/honest-bears-itch.md
deleted file mode 100644
index 4c0cd5f47e..0000000000
--- a/.changeset/honest-bears-itch.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-test-utils': patch
----
-
-Switched `textextensions` dependency for `text-extensions`.
diff --git a/.changeset/hot-colts-float.md b/.changeset/hot-colts-float.md
new file mode 100644
index 0000000000..093dcfb12a
--- /dev/null
+++ b/.changeset/hot-colts-float.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers).
+
+Affected components: SearchField
diff --git a/.changeset/kind-hoops-double.md b/.changeset/kind-hoops-double.md
deleted file mode 100644
index fd2f9f023f..0000000000
--- a/.changeset/kind-hoops-double.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/integration': patch
----
-
-Made the github urls case insensitive.
diff --git a/.changeset/large-planes-punch.md b/.changeset/large-planes-punch.md
deleted file mode 100644
index 3216653eb9..0000000000
--- a/.changeset/large-planes-punch.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-defaults': patch
----
-
-Fix for `jose` types
diff --git a/.changeset/legal-cloths-spend.md b/.changeset/legal-cloths-spend.md
deleted file mode 100644
index 50d965307a..0000000000
--- a/.changeset/legal-cloths-spend.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-mcp-actions-backend': patch
----
-
-Clarify error handling in readme and update handleError.ts to include all backstage/errors
diff --git a/.changeset/legal-otters-punch.md b/.changeset/legal-otters-punch.md
deleted file mode 100644
index 2003492e29..0000000000
--- a/.changeset/legal-otters-punch.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/plugin-kubernetes-backend': patch
-'@backstage/plugin-kubernetes-common': patch
-'@backstage/plugin-kubernetes-react': patch
----
-
-Add Kubernetes Plugin Secrets Accordion with masked secret datas
diff --git a/.changeset/lemon-corners-hug.md b/.changeset/lemon-corners-hug.md
deleted file mode 100644
index b41c09c928..0000000000
--- a/.changeset/lemon-corners-hug.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-techdocs-node': patch
----
-
-There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality.
diff --git a/.changeset/loose-pets-slide.md b/.changeset/loose-pets-slide.md
deleted file mode 100644
index b8ec201f1c..0000000000
--- a/.changeset/loose-pets-slide.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-'@backstage/repo-tools': patch
----
-
-Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting.
-
-- Updates modelGeneric.mustache templates in both client and server generators
-- Updates modelTaggedUnion.mustache templates in both client and server generators
-- Uses {{baseName}} when available, falls back to {{name}} for backward compatibility
-- Maintains backward compatibility - no changes when propertyNaming=original is not used
diff --git a/.changeset/loud-yaks-watch.md b/.changeset/loud-yaks-watch.md
deleted file mode 100644
index b7ad65b9eb..0000000000
--- a/.changeset/loud-yaks-watch.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/repo-tools': patch
----
-
-Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions.
diff --git a/.changeset/lucky-days-hug.md b/.changeset/lucky-days-hug.md
deleted file mode 100644
index 4d76caa098..0000000000
--- a/.changeset/lucky-days-hug.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-kubernetes-react': patch
-'@backstage/plugin-kubernetes': patch
----
-
-add missing i18n support for kubernetes and kubernetes-react
diff --git a/.changeset/major-mangos-shine.md b/.changeset/major-mangos-shine.md
new file mode 100644
index 0000000000..f42fcf5d0e
--- /dev/null
+++ b/.changeset/major-mangos-shine.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-openapi-utils': patch
+---
+
+Internal update to use native feature from our request validation library for handling base path determination.
diff --git a/.changeset/many-planes-join.md b/.changeset/many-planes-join.md
deleted file mode 100644
index 66107cd370..0000000000
--- a/.changeset/many-planes-join.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/core-app-api': patch
-'@backstage/test-utils': patch
----
-
-Internal update of translation imports.
diff --git a/.changeset/metal-boxes-laugh.md b/.changeset/metal-boxes-laugh.md
deleted file mode 100644
index 9a5ab24e0e..0000000000
--- a/.changeset/metal-boxes-laugh.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core-plugin-api': patch
----
-
-The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs.
diff --git a/.changeset/metal-humans-lose.md b/.changeset/metal-humans-lose.md
deleted file mode 100644
index f96d33a779..0000000000
--- a/.changeset/metal-humans-lose.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-'@backstage/plugin-catalog-unprocessed-entities': patch
-'@backstage/plugin-app-visualizer': patch
-'@backstage/plugin-catalog-import': patch
-'@backstage/plugin-catalog-graph': patch
-'@backstage/plugin-catalog-react': patch
-'@backstage/plugin-notifications': patch
-'@backstage/plugin-user-settings': 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-app': patch
-'@backstage/plugin-org': patch
----
-
-Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
diff --git a/.changeset/modern-taxes-start.md b/.changeset/modern-taxes-start.md
deleted file mode 100644
index 9fd7645570..0000000000
--- a/.changeset/modern-taxes-start.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli-common': patch
----
-
-Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way.
diff --git a/.changeset/neat-pens-clean.md b/.changeset/neat-pens-clean.md
deleted file mode 100644
index aff67a2a10..0000000000
--- a/.changeset/neat-pens-clean.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/repo-tools': patch
----
-
-Properly create workspace in OS temporary directory for `generate-patch` command
diff --git a/.changeset/nice-humans-cry.md b/.changeset/nice-humans-cry.md
deleted file mode 100644
index c6119702b4..0000000000
--- a/.changeset/nice-humans-cry.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
-'@backstage/backend-defaults': patch
----
-
-Internal change to support new versions of the `logform` library
diff --git a/.changeset/nice-trams-shake.md b/.changeset/nice-trams-shake.md
deleted file mode 100644
index b58c03200f..0000000000
--- a/.changeset/nice-trams-shake.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-react': patch
----
-
-Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written.
diff --git a/.changeset/old-cats-shake.md b/.changeset/old-cats-shake.md
deleted file mode 100644
index 3ca30a0421..0000000000
--- a/.changeset/old-cats-shake.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/backend-defaults': patch
-'@backstage/backend-plugin-api': minor
----
-
-Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
diff --git a/.changeset/old-parks-smell.md b/.changeset/old-parks-smell.md
deleted file mode 100644
index f089c7da21..0000000000
--- a/.changeset/old-parks-smell.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-'@backstage/plugin-techdocs-addons-test-utils': major
----
-
-**BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value.
-
-Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used.
-
-One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead.
-
-```ts
-import { screen } from 'shadow-dom-testing-library';
-
-// ... render the addon ...
-await TechDocsAddonTester.buildAddonsInTechDocs([])
- .withDom(
TEST_CONTENT)
- .renderWithEffects();
-
-expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument();
-```
diff --git a/.changeset/open-points-beam.md b/.changeset/open-points-beam.md
deleted file mode 100644
index b58bf2b3d5..0000000000
--- a/.changeset/open-points-beam.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-'@backstage/ui': minor
----
-
-**Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component.
-
-### Migration Guide
-
-If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead:
-
-**Before:**
-
-```tsx
-}
- href="/path"
-/>
-```
-
-**After:**
-
-```tsx
-}
- href="/path"
-/>
-```
-
-For custom cell content, use the new generic `Cell` component:
-
-```tsx
-{/* Your custom content */}
-```
diff --git a/.changeset/polite-pillows-accept.md b/.changeset/polite-pillows-accept.md
new file mode 100644
index 0000000000..ece7c02ee1
--- /dev/null
+++ b/.changeset/polite-pillows-accept.md
@@ -0,0 +1,5 @@
+---
+'@backstage/integration': patch
+---
+
+Rollback the lowercase replacing in GitHub integration config
diff --git a/.changeset/pre.json b/.changeset/pre.json
index f44b869497..9b396bf7a7 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -2,256 +2,221 @@
"mode": "pre",
"tag": "next",
"initialVersions": {
- "example-app": "0.2.115",
- "@backstage/app-defaults": "1.7.2",
- "example-app-next": "0.0.29",
- "app-next-example-plugin": "0.0.29",
- "example-backend": "0.0.44",
- "@backstage/backend-app-api": "1.3.0",
- "@backstage/backend-defaults": "0.13.1",
- "@backstage/backend-dev-utils": "0.1.5",
- "@backstage/backend-dynamic-feature-service": "0.7.6",
- "@backstage/backend-openapi-utils": "0.6.3",
- "@backstage/backend-plugin-api": "1.5.0",
- "@backstage/backend-test-utils": "1.10.0",
+ "example-app": "0.2.116",
+ "@backstage/app-defaults": "1.7.3",
+ "example-app-next": "0.0.30",
+ "app-next-example-plugin": "0.0.30",
+ "example-backend": "0.0.45",
+ "@backstage/backend-app-api": "1.4.0",
+ "@backstage/backend-defaults": "0.14.0",
+ "@backstage/backend-dev-utils": "0.1.6",
+ "@backstage/backend-dynamic-feature-service": "0.7.7",
+ "@backstage/backend-openapi-utils": "0.6.4",
+ "@backstage/backend-plugin-api": "1.6.0",
+ "@backstage/backend-test-utils": "1.10.2",
"@backstage/catalog-client": "1.12.1",
"@backstage/catalog-model": "1.7.6",
- "@backstage/cli": "0.34.5",
- "@backstage/cli-common": "0.1.15",
- "@backstage/cli-node": "0.2.15",
- "@backstage/codemods": "0.1.52",
+ "@backstage/cli": "0.35.0",
+ "@backstage/cli-common": "0.1.16",
+ "@backstage/cli-node": "0.2.16",
+ "@backstage/codemods": "0.1.53",
"@backstage/config": "1.3.6",
- "@backstage/config-loader": "1.10.6",
- "@backstage/core-app-api": "1.19.2",
- "@backstage/core-compat-api": "0.5.4",
- "@backstage/core-components": "0.18.3",
- "@backstage/core-plugin-api": "1.12.0",
- "@backstage/create-app": "0.7.6",
- "@backstage/dev-utils": "1.1.17",
- "e2e-test": "0.2.34",
+ "@backstage/config-loader": "1.10.7",
+ "@backstage/core-app-api": "1.19.3",
+ "@backstage/core-compat-api": "0.5.5",
+ "@backstage/core-components": "0.18.4",
+ "@backstage/core-plugin-api": "1.12.1",
+ "@backstage/create-app": "0.7.7",
+ "@backstage/dev-utils": "1.1.18",
+ "e2e-test": "0.2.35",
"@backstage/e2e-test-utils": "0.1.1",
"@backstage/errors": "1.2.7",
"@backstage/eslint-plugin": "0.2.0",
- "@backstage/frontend-app-api": "0.13.2",
- "@backstage/frontend-defaults": "0.3.3",
- "@backstage/frontend-dynamic-feature-loader": "0.1.7",
- "@internal/frontend": "0.0.15",
- "@backstage/frontend-plugin-api": "0.13.0",
- "@backstage/frontend-test-utils": "0.4.1",
- "@backstage/integration": "1.18.2",
+ "@backstage/frontend-app-api": "0.13.3",
+ "@backstage/frontend-defaults": "0.3.4",
+ "@backstage/frontend-dynamic-feature-loader": "0.1.8",
+ "@internal/frontend": "0.0.16",
+ "@backstage/frontend-plugin-api": "0.13.2",
+ "@backstage/frontend-test-utils": "0.4.2",
+ "@backstage/integration": "1.19.0",
"@backstage/integration-aws-node": "0.1.19",
- "@backstage/integration-react": "1.2.12",
+ "@backstage/integration-react": "1.2.13",
"@internal/opaque": "0.0.1",
"@backstage/release-manifests": "0.0.13",
- "@backstage/repo-tools": "0.16.0",
- "@internal/scaffolder": "0.0.15",
- "@techdocs/cli": "1.10.2",
- "techdocs-cli-embedded-app": "0.2.114",
- "@backstage/test-utils": "1.7.13",
- "@backstage/theme": "0.7.0",
+ "@backstage/repo-tools": "0.16.1",
+ "@internal/scaffolder": "0.0.16",
+ "@techdocs/cli": "1.10.3",
+ "techdocs-cli-embedded-app": "0.2.115",
+ "@backstage/test-utils": "1.7.14",
+ "@backstage/theme": "0.7.1",
"@backstage/types": "1.2.2",
- "@backstage/ui": "0.9.0",
+ "@backstage/ui": "0.10.0",
"@backstage/version-bridge": "1.0.11",
- "yarn-plugin-backstage": "0.0.7",
- "@backstage/plugin-api-docs": "0.13.1",
+ "yarn-plugin-backstage": "0.0.8",
+ "@backstage/plugin-api-docs": "0.13.2",
"@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10",
- "@backstage/plugin-app": "0.3.2",
- "@backstage/plugin-app-backend": "0.5.8",
- "@backstage/plugin-app-node": "0.1.39",
- "@backstage/plugin-app-visualizer": "0.1.25",
- "@backstage/plugin-auth": "0.1.2",
- "@backstage/plugin-auth-backend": "0.25.6",
- "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.9",
- "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.14",
- "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.9",
- "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-github-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-google-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-guest-provider": "0.2.14",
- "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.14",
- "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.9",
- "@backstage/plugin-auth-backend-module-okta-provider": "0.2.9",
- "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.2",
- "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.9",
- "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.9",
- "@backstage/plugin-auth-node": "0.6.9",
- "@backstage/plugin-auth-react": "0.1.21",
- "@backstage/plugin-bitbucket-cloud-common": "0.3.4",
- "@backstage/plugin-catalog": "1.32.0",
- "@backstage/plugin-catalog-backend": "3.2.0",
- "@backstage/plugin-catalog-backend-module-aws": "0.4.17",
- "@backstage/plugin-catalog-backend-module-azure": "0.3.11",
- "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.8",
- "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.5",
- "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.5",
- "@backstage/plugin-catalog-backend-module-gcp": "0.3.14",
- "@backstage/plugin-catalog-backend-module-gerrit": "0.3.8",
- "@backstage/plugin-catalog-backend-module-gitea": "0.1.6",
- "@backstage/plugin-catalog-backend-module-github": "0.11.2",
- "@backstage/plugin-catalog-backend-module-github-org": "0.3.16",
- "@backstage/plugin-catalog-backend-module-gitlab": "0.7.5",
- "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.15",
- "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.6",
- "@backstage/plugin-catalog-backend-module-ldap": "0.12.0",
- "@backstage/plugin-catalog-backend-module-logs": "0.1.16",
- "@backstage/plugin-catalog-backend-module-msgraph": "0.8.2",
- "@backstage/plugin-catalog-backend-module-openapi": "0.2.16",
- "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.16",
- "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.14",
- "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.6",
+ "@backstage/plugin-app": "0.3.3",
+ "@backstage/plugin-app-backend": "0.5.9",
+ "@backstage/plugin-app-node": "0.1.40",
+ "@backstage/plugin-app-visualizer": "0.1.26",
+ "@backstage/plugin-auth": "0.1.3",
+ "@backstage/plugin-auth-backend": "0.25.7",
+ "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.10",
+ "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.15",
+ "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.10",
+ "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-github-provider": "0.4.0",
+ "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-google-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-guest-provider": "0.2.15",
+ "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.15",
+ "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.10",
+ "@backstage/plugin-auth-backend-module-okta-provider": "0.2.10",
+ "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.3",
+ "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.10",
+ "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.10",
+ "@backstage/plugin-auth-node": "0.6.10",
+ "@backstage/plugin-auth-react": "0.1.22",
+ "@backstage/plugin-bitbucket-cloud-common": "0.3.5",
+ "@backstage/plugin-catalog": "1.32.1",
+ "@backstage/plugin-catalog-backend": "3.3.0",
+ "@backstage/plugin-catalog-backend-module-aws": "0.4.18",
+ "@backstage/plugin-catalog-backend-module-azure": "0.3.12",
+ "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.9",
+ "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.6",
+ "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.6",
+ "@backstage/plugin-catalog-backend-module-gcp": "0.3.15",
+ "@backstage/plugin-catalog-backend-module-gerrit": "0.3.9",
+ "@backstage/plugin-catalog-backend-module-gitea": "0.1.7",
+ "@backstage/plugin-catalog-backend-module-github": "0.12.0",
+ "@backstage/plugin-catalog-backend-module-github-org": "0.3.17",
+ "@backstage/plugin-catalog-backend-module-gitlab": "0.7.6",
+ "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.16",
+ "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.7",
+ "@backstage/plugin-catalog-backend-module-ldap": "0.12.1",
+ "@backstage/plugin-catalog-backend-module-logs": "0.1.17",
+ "@backstage/plugin-catalog-backend-module-msgraph": "0.8.3",
+ "@backstage/plugin-catalog-backend-module-openapi": "0.2.17",
+ "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.17",
+ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.15",
+ "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.7",
"@backstage/plugin-catalog-common": "1.1.7",
- "@backstage/plugin-catalog-graph": "0.5.3",
- "@backstage/plugin-catalog-import": "0.13.7",
- "@backstage/plugin-catalog-node": "1.20.0",
- "@backstage/plugin-catalog-react": "1.21.3",
- "@backstage/plugin-catalog-unprocessed-entities": "0.2.23",
- "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.11",
- "@backstage/plugin-config-schema": "0.1.74",
- "@backstage/plugin-devtools": "0.1.33",
- "@backstage/plugin-devtools-backend": "0.5.11",
- "@backstage/plugin-devtools-common": "0.1.19",
- "@backstage/plugin-events-backend": "0.5.8",
- "@backstage/plugin-events-backend-module-aws-sqs": "0.4.17",
- "@backstage/plugin-events-backend-module-azure": "0.2.26",
- "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.26",
- "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.7",
- "@backstage/plugin-events-backend-module-gerrit": "0.2.26",
- "@backstage/plugin-events-backend-module-github": "0.4.6",
- "@backstage/plugin-events-backend-module-gitlab": "0.3.7",
- "@backstage/plugin-events-backend-module-google-pubsub": "0.1.6",
- "@backstage/plugin-events-backend-module-kafka": "0.1.5",
- "@backstage/plugin-events-backend-test-utils": "0.1.50",
- "@backstage/plugin-events-node": "0.4.17",
- "@internal/plugin-todo-list": "1.0.45",
- "@internal/plugin-todo-list-backend": "1.0.45",
+ "@backstage/plugin-catalog-graph": "0.5.4",
+ "@backstage/plugin-catalog-import": "0.13.8",
+ "@backstage/plugin-catalog-node": "1.20.1",
+ "@backstage/plugin-catalog-react": "1.21.4",
+ "@backstage/plugin-catalog-unprocessed-entities": "0.2.24",
+ "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.12",
+ "@backstage/plugin-config-schema": "0.1.75",
+ "@backstage/plugin-devtools": "0.1.34",
+ "@backstage/plugin-devtools-backend": "0.5.12",
+ "@backstage/plugin-devtools-common": "0.1.20",
+ "@backstage/plugin-events-backend": "0.5.9",
+ "@backstage/plugin-events-backend-module-aws-sqs": "0.4.18",
+ "@backstage/plugin-events-backend-module-azure": "0.2.27",
+ "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.27",
+ "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.8",
+ "@backstage/plugin-events-backend-module-gerrit": "0.2.27",
+ "@backstage/plugin-events-backend-module-github": "0.4.7",
+ "@backstage/plugin-events-backend-module-gitlab": "0.3.8",
+ "@backstage/plugin-events-backend-module-google-pubsub": "0.1.7",
+ "@backstage/plugin-events-backend-module-kafka": "0.2.0",
+ "@backstage/plugin-events-backend-test-utils": "0.1.51",
+ "@backstage/plugin-events-node": "0.4.18",
+ "@internal/plugin-todo-list": "1.0.46",
+ "@internal/plugin-todo-list-backend": "1.0.46",
"@internal/plugin-todo-list-common": "1.0.28",
- "@backstage/plugin-gateway-backend": "1.1.0",
- "@backstage/plugin-home": "0.8.14",
- "@backstage/plugin-home-react": "0.1.32",
- "@backstage/plugin-kubernetes": "0.12.13",
- "@backstage/plugin-kubernetes-backend": "0.20.4",
- "@backstage/plugin-kubernetes-cluster": "0.0.31",
- "@backstage/plugin-kubernetes-common": "0.9.8",
- "@backstage/plugin-kubernetes-node": "0.3.6",
- "@backstage/plugin-kubernetes-react": "0.5.13",
- "@backstage/plugin-mcp-actions-backend": "0.1.5",
- "@backstage/plugin-mui-to-bui": "0.2.1",
- "@backstage/plugin-notifications": "0.5.11",
- "@backstage/plugin-notifications-backend": "0.6.0",
- "@backstage/plugin-notifications-backend-module-email": "0.3.16",
- "@backstage/plugin-notifications-backend-module-slack": "0.2.1",
+ "@backstage/plugin-gateway-backend": "1.1.1",
+ "@backstage/plugin-home": "0.8.15",
+ "@backstage/plugin-home-react": "0.1.33",
+ "@backstage/plugin-kubernetes": "0.12.14",
+ "@backstage/plugin-kubernetes-backend": "0.21.0",
+ "@backstage/plugin-kubernetes-cluster": "0.0.32",
+ "@backstage/plugin-kubernetes-common": "0.9.9",
+ "@backstage/plugin-kubernetes-node": "0.4.0",
+ "@backstage/plugin-kubernetes-react": "0.5.14",
+ "@backstage/plugin-mcp-actions-backend": "0.1.6",
+ "@backstage/plugin-mui-to-bui": "0.2.2",
+ "@backstage/plugin-notifications": "0.5.12",
+ "@backstage/plugin-notifications-backend": "0.6.1",
+ "@backstage/plugin-notifications-backend-module-email": "0.3.17",
+ "@backstage/plugin-notifications-backend-module-slack": "0.3.0",
"@backstage/plugin-notifications-common": "0.2.0",
- "@backstage/plugin-notifications-node": "0.2.21",
- "@backstage/plugin-org": "0.6.46",
- "@backstage/plugin-org-react": "0.1.44",
- "@backstage/plugin-permission-backend": "0.7.6",
- "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.14",
+ "@backstage/plugin-notifications-node": "0.2.22",
+ "@backstage/plugin-org": "0.6.47",
+ "@backstage/plugin-org-react": "0.1.45",
+ "@backstage/plugin-permission-backend": "0.7.7",
+ "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.15",
"@backstage/plugin-permission-common": "0.9.3",
- "@backstage/plugin-permission-node": "0.10.6",
- "@backstage/plugin-permission-react": "0.4.38",
- "@backstage/plugin-proxy-backend": "0.6.8",
- "@backstage/plugin-proxy-node": "0.1.10",
- "@backstage/plugin-scaffolder": "1.34.3",
- "@backstage/plugin-scaffolder-backend": "3.0.1",
- "@backstage/plugin-scaffolder-backend-module-azure": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.16",
- "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.15",
- "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.17",
- "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-github": "0.9.2",
- "@backstage/plugin-scaffolder-backend-module-gitlab": "0.10.0",
- "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.16",
- "@backstage/plugin-scaffolder-backend-module-rails": "0.5.15",
- "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.16",
- "@backstage/plugin-scaffolder-common": "1.7.3",
- "@backstage/plugin-scaffolder-node": "0.12.1",
- "@backstage/plugin-scaffolder-node-test-utils": "0.3.5",
- "@backstage/plugin-scaffolder-react": "1.19.3",
- "@backstage/plugin-search": "1.5.0",
- "@backstage/plugin-search-backend": "2.0.8",
- "@backstage/plugin-search-backend-module-catalog": "0.3.10",
- "@backstage/plugin-search-backend-module-elasticsearch": "1.7.8",
- "@backstage/plugin-search-backend-module-explore": "0.3.9",
- "@backstage/plugin-search-backend-module-pg": "0.5.50",
- "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.15",
- "@backstage/plugin-search-backend-module-techdocs": "0.4.8",
- "@backstage/plugin-search-backend-node": "1.3.17",
+ "@backstage/plugin-permission-node": "0.10.7",
+ "@backstage/plugin-permission-react": "0.4.39",
+ "@backstage/plugin-proxy-backend": "0.6.9",
+ "@backstage/plugin-proxy-node": "0.1.11",
+ "@backstage/plugin-scaffolder": "1.35.0",
+ "@backstage/plugin-scaffolder-backend": "3.1.0",
+ "@backstage/plugin-scaffolder-backend-module-azure": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.17",
+ "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.0",
+ "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.16",
+ "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.18",
+ "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-github": "0.9.3",
+ "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.0",
+ "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.17",
+ "@backstage/plugin-scaffolder-backend-module-rails": "0.5.16",
+ "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.16",
+ "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.17",
+ "@backstage/plugin-scaffolder-common": "1.7.4",
+ "@backstage/plugin-scaffolder-node": "0.12.2",
+ "@backstage/plugin-scaffolder-node-test-utils": "0.3.6",
+ "@backstage/plugin-scaffolder-react": "1.19.4",
+ "@backstage/plugin-search": "1.5.1",
+ "@backstage/plugin-search-backend": "2.0.9",
+ "@backstage/plugin-search-backend-module-catalog": "0.3.11",
+ "@backstage/plugin-search-backend-module-elasticsearch": "1.7.9",
+ "@backstage/plugin-search-backend-module-explore": "0.3.10",
+ "@backstage/plugin-search-backend-module-pg": "0.5.51",
+ "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.16",
+ "@backstage/plugin-search-backend-module-techdocs": "0.4.9",
+ "@backstage/plugin-search-backend-node": "1.4.0",
"@backstage/plugin-search-common": "1.2.21",
- "@backstage/plugin-search-react": "1.10.0",
- "@backstage/plugin-signals": "0.0.25",
- "@backstage/plugin-signals-backend": "0.3.10",
- "@backstage/plugin-signals-node": "0.1.26",
- "@backstage/plugin-signals-react": "0.0.17",
- "@backstage/plugin-techdocs": "1.16.0",
- "@backstage/plugin-techdocs-addons-test-utils": "1.1.2",
- "@backstage/plugin-techdocs-backend": "2.1.2",
+ "@backstage/plugin-search-react": "1.10.1",
+ "@backstage/plugin-signals": "0.0.26",
+ "@backstage/plugin-signals-backend": "0.3.11",
+ "@backstage/plugin-signals-node": "0.1.27",
+ "@backstage/plugin-signals-react": "0.0.18",
+ "@backstage/plugin-techdocs": "1.16.1",
+ "@backstage/plugin-techdocs-addons-test-utils": "2.0.0",
+ "@backstage/plugin-techdocs-backend": "2.1.3",
"@backstage/plugin-techdocs-common": "0.1.1",
- "@backstage/plugin-techdocs-module-addons-contrib": "1.1.30",
- "@backstage/plugin-techdocs-node": "1.13.9",
- "@backstage/plugin-techdocs-react": "1.3.5",
- "@backstage/plugin-user-settings": "0.8.29",
- "@backstage/plugin-user-settings-backend": "0.3.8",
+ "@backstage/plugin-techdocs-module-addons-contrib": "1.1.31",
+ "@backstage/plugin-techdocs-node": "1.13.10",
+ "@backstage/plugin-techdocs-react": "1.3.6",
+ "@backstage/plugin-user-settings": "0.8.30",
+ "@backstage/plugin-user-settings-backend": "0.3.9",
"@backstage/plugin-user-settings-common": "0.0.1"
},
"changesets": [
- "afraid-items-drum",
- "bumpy-planets-go",
- "chatty-sides-wear",
- "chilly-bikes-rule",
- "chilly-hotels-walk",
- "clean-toys-reply",
- "common-coins-stare",
- "create-app-1764689798",
- "famous-jars-lose",
- "fifty-coats-feel",
- "fine-eagles-sleep",
- "flat-pillows-rush",
- "floppy-bobcats-serve",
- "four-peaches-train",
- "fruity-rivers-arrive",
- "fruity-words-melt",
- "funny-papayas-rest",
- "fuzzy-phones-own",
- "fuzzy-trees-live",
- "gentle-trains-juggle",
- "great-files-shave",
- "happy-bottles-invite",
- "kind-hoops-double",
- "legal-cloths-spend",
- "legal-otters-punch",
- "loose-pets-slide",
- "lucky-days-hug",
- "many-planes-join",
- "metal-boxes-laugh",
- "metal-humans-lose",
- "modern-taxes-start",
- "neat-pens-clean",
- "nice-trams-shake",
- "old-parks-smell",
- "open-points-beam",
- "quiet-hats-sleep",
- "rotten-melons-sleep",
- "short-groups-knock",
- "slick-books-sleep",
- "slick-onions-wash",
- "slimy-islands-play",
- "spicy-teeth-study",
- "stale-eagles-rush",
- "tender-dancers-hunt",
- "tough-lies-grow",
- "twenty-ducks-relate"
+ "better-ghosts-tell",
+ "create-app-1766498726",
+ "dependabot-aed8e01",
+ "every-bears-add",
+ "hot-colts-float",
+ "major-mangos-shine",
+ "proud-streets-switch",
+ "shaggy-bats-film",
+ "thin-teams-roll",
+ "weak-lemons-create",
+ "wild-toys-retire"
]
}
diff --git a/.changeset/proud-streets-switch.md b/.changeset/proud-streets-switch.md
new file mode 100644
index 0000000000..188b66f178
--- /dev/null
+++ b/.changeset/proud-streets-switch.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend-module-oidc-provider': patch
+---
+
+Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property)
diff --git a/.changeset/quiet-hats-sleep.md b/.changeset/quiet-hats-sleep.md
deleted file mode 100644
index 5f75f22f6c..0000000000
--- a/.changeset/quiet-hats-sleep.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Fixing OpenAPI definition
diff --git a/.changeset/rare-rice-throw.md b/.changeset/rare-rice-throw.md
deleted file mode 100644
index 6fa0434f67..0000000000
--- a/.changeset/rare-rice-throw.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core-components': patch
----
-
-Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip
diff --git a/.changeset/ready-results-march.md b/.changeset/ready-results-march.md
deleted file mode 100644
index c28ade8de9..0000000000
--- a/.changeset/ready-results-march.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog': patch
----
-
-Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links
diff --git a/.changeset/renovate-7b76d6d.md b/.changeset/renovate-7b76d6d.md
deleted file mode 100644
index 9ccb2dd94b..0000000000
--- a/.changeset/renovate-7b76d6d.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/cli': patch
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Updated dependency `esbuild` to `^0.27.0`.
diff --git a/.changeset/renovate-959c095.md b/.changeset/renovate-959c095.md
deleted file mode 100644
index d2370a1c8b..0000000000
--- a/.changeset/renovate-959c095.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@backstage/plugin-home-react': patch
-'@backstage/plugin-home': patch
-'@backstage/plugin-scaffolder-react': patch
-'@backstage/plugin-scaffolder': patch
----
-
-Updated dependency `@rjsf/utils` to `5.24.13`.
-Updated dependency `@rjsf/core` to `5.24.13`.
-Updated dependency `@rjsf/material-ui` to `5.24.13`.
-Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
diff --git a/.changeset/renovate-97eef4b.md b/.changeset/renovate-97eef4b.md
deleted file mode 100644
index 9b3692bc1c..0000000000
--- a/.changeset/renovate-97eef4b.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/config-loader': patch
----
-
-Updated dependency `typescript-json-schema` to `^0.67.0`.
diff --git a/.changeset/renovate-9ec01ff.md b/.changeset/renovate-9ec01ff.md
deleted file mode 100644
index 1ab750c0e6..0000000000
--- a/.changeset/renovate-9ec01ff.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-search-backend-module-explore': patch
----
-
-Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`.
diff --git a/.changeset/renovate-a3c2cbd.md b/.changeset/renovate-a3c2cbd.md
deleted file mode 100644
index 2fde85d39e..0000000000
--- a/.changeset/renovate-a3c2cbd.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-react': patch
----
-
-Updated dependency `use-immer` to `^0.11.0`.
diff --git a/.changeset/renovate-aa0bb93.md b/.changeset/renovate-aa0bb93.md
deleted file mode 100644
index 5f40a61fab..0000000000
--- a/.changeset/renovate-aa0bb93.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-notifications-backend-module-email': patch
----
-
-Updated dependency `@types/nodemailer` to `^7.0.0`.
diff --git a/.changeset/renovate-ad7bf17.md b/.changeset/renovate-ad7bf17.md
deleted file mode 100644
index a0f90ff97d..0000000000
--- a/.changeset/renovate-ad7bf17.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-defaults': patch
----
-
-Updated dependency `@types/archiver` to `^7.0.0`.
diff --git a/.changeset/renovate-d7ceb06.md b/.changeset/renovate-d7ceb06.md
deleted file mode 100644
index d22f266301..0000000000
--- a/.changeset/renovate-d7ceb06.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`.
diff --git a/.changeset/renovate-ea25c93.md b/.changeset/renovate-ea25c93.md
deleted file mode 100644
index 958e032b64..0000000000
--- a/.changeset/renovate-ea25c93.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Updated dependency `webpack` to `~5.103.0`.
diff --git a/.changeset/rotten-melons-sleep.md b/.changeset/rotten-melons-sleep.md
deleted file mode 100644
index c12e0a9669..0000000000
--- a/.changeset/rotten-melons-sleep.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-'@backstage/plugin-catalog-unprocessed-entities-common': patch
-'@backstage/plugin-catalog-unprocessed-entities': patch
----
-
-Moved types, API and client to the common package, allowing both frontend and
-backend plugins to use the `CatalogUnprocessedEntitiesClient`.
-
-The following types, clients and interfaces have been deprecated and should be
-imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead:
-`CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`,
-`UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`.
-
-All those types, clients and interfaces are re-exported temporarily in the
-`@backstage/plugin-catalog-unprocessed-entities` package until cleaned up.
diff --git a/.changeset/salty-camels-wash.md b/.changeset/salty-camels-wash.md
deleted file mode 100644
index 56600db540..0000000000
--- a/.changeset/salty-camels-wash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Fixed internal usage of `yargs`.
diff --git a/.changeset/seven-games-rest.md b/.changeset/seven-games-rest.md
deleted file mode 100644
index b498c3b138..0000000000
--- a/.changeset/seven-games-rest.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-techdocs-backend': patch
----
-
-Corrected `ErrorCallback` type to work with Node 22 types
diff --git a/.changeset/shaggy-bats-film.md b/.changeset/shaggy-bats-film.md
new file mode 100644
index 0000000000..3e5be9648b
--- /dev/null
+++ b/.changeset/shaggy-bats-film.md
@@ -0,0 +1,5 @@
+---
+'@backstage/ui': minor
+---
+
+**BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency.
diff --git a/.changeset/short-groups-knock.md b/.changeset/short-groups-knock.md
deleted file mode 100644
index 0b622a71ed..0000000000
--- a/.changeset/short-groups-knock.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/cli-common': patch
-'@backstage/create-app': patch
-'@backstage/cli': patch
----
-
-Add proxy configuration from env-vars to create-app tasks
diff --git a/.changeset/slick-books-sleep.md b/.changeset/slick-books-sleep.md
deleted file mode 100644
index 7aa3be5b03..0000000000
--- a/.changeset/slick-books-sleep.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/theme': patch
----
-
-Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme.
diff --git a/.changeset/slick-onions-wash.md b/.changeset/slick-onions-wash.md
deleted file mode 100644
index 4627d02e81..0000000000
--- a/.changeset/slick-onions-wash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core-components': patch
----
-
-Add i18n support for LogViewer search control
diff --git a/.changeset/slimy-islands-play.md b/.changeset/slimy-islands-play.md
deleted file mode 100644
index 497d86761b..0000000000
--- a/.changeset/slimy-islands-play.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.
-
-Affected components: Table, TableHeader, Row, Column
diff --git a/.changeset/slimy-mugs-taste.md b/.changeset/slimy-mugs-taste.md
deleted file mode 100644
index 5773db6853..0000000000
--- a/.changeset/slimy-mugs-taste.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-test-utils': patch
----
-
-Fix PostgreSQL 18 `TestDatabases` by pinning the data directory
diff --git a/.changeset/sour-bats-press.md b/.changeset/sour-bats-press.md
deleted file mode 100644
index bfcf3985e6..0000000000
--- a/.changeset/sour-bats-press.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-auth-node': patch
----
-
-fix flawed cookie removal logic with chunked tokens
diff --git a/.changeset/spicy-teeth-study.md b/.changeset/spicy-teeth-study.md
deleted file mode 100644
index 03cc8a5440..0000000000
--- a/.changeset/spicy-teeth-study.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios.
diff --git a/.changeset/stale-eagles-rush.md b/.changeset/stale-eagles-rush.md
deleted file mode 100644
index fe640ccd44..0000000000
--- a/.changeset/stale-eagles-rush.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/frontend-plugin-api': patch
-'@backstage/core-plugin-api': patch
----
-
-Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old.
diff --git a/.changeset/stupid-cases-fold.md b/.changeset/stupid-cases-fold.md
deleted file mode 100644
index 5cf0f0f72a..0000000000
--- a/.changeset/stupid-cases-fold.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Switched ECMAScript version to ES2023.
diff --git a/.changeset/tall-ideas-lead.md b/.changeset/tall-ideas-lead.md
deleted file mode 100644
index 60bef903d8..0000000000
--- a/.changeset/tall-ideas-lead.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch
----
-
-Improve error message when provided target branch is missing
diff --git a/.changeset/tame-mirrors-sit.md b/.changeset/tame-mirrors-sit.md
deleted file mode 100644
index f317185ea2..0000000000
--- a/.changeset/tame-mirrors-sit.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-dev-utils': patch
----
-
-Internal update for Node.js v24 support.
diff --git a/.changeset/tender-dancers-hunt.md b/.changeset/tender-dancers-hunt.md
deleted file mode 100644
index e20ec43ae0..0000000000
--- a/.changeset/tender-dancers-hunt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Updated to use new utilities from `@backstage/cli-common`.
diff --git a/.changeset/thin-teams-roll.md b/.changeset/thin-teams-roll.md
new file mode 100644
index 0000000000..f0c395410a
--- /dev/null
+++ b/.changeset/thin-teams-roll.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+Fixed a spelling mistake in root health service shutdown response.
diff --git a/.changeset/tired-dogs-remain.md b/.changeset/tired-dogs-remain.md
deleted file mode 100644
index 06c193068b..0000000000
--- a/.changeset/tired-dogs-remain.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend-module-rails': patch
-'@backstage/plugin-kubernetes-cluster': patch
-'@techdocs/cli': patch
-'@backstage/cli-common': patch
-'@backstage/create-app': patch
-'@backstage/repo-tools': patch
-'@backstage/codemods': patch
-'@backstage/cli': patch
----
-
-Bumped dev dependencies `@types/node`
diff --git a/.changeset/tough-lies-grow.md b/.changeset/tough-lies-grow.md
deleted file mode 100644
index dab952df65..0000000000
--- a/.changeset/tough-lies-grow.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-techdocs-backend': patch
-'@backstage/plugin-techdocs': patch
----
-
-The `techdocs` config is now marked as optional.
diff --git a/.changeset/twenty-ducks-relate.md b/.changeset/twenty-ducks-relate.md
deleted file mode 100644
index 359eda66f3..0000000000
--- a/.changeset/twenty-ducks-relate.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/ui': patch
----
-
-Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending.
-
-Affected components: Column
diff --git a/.changeset/weak-lemons-create.md b/.changeset/weak-lemons-create.md
new file mode 100644
index 0000000000..344a6660ce
--- /dev/null
+++ b/.changeset/weak-lemons-create.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Bump minimum required `@swc/core` to avoid transpilation bug
diff --git a/.changeset/wild-toys-retire.md b/.changeset/wild-toys-retire.md
new file mode 100644
index 0000000000..f7179e2527
--- /dev/null
+++ b/.changeset/wild-toys-retire.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-mui-to-bui': patch
+---
+
+Updated tokens from `--bui-bg` to `--bui-bg-surface-0`
diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt
index 6697fad858..44ee42a25a 100644
--- a/.github/vale/config/vocabularies/Backstage/accept.txt
+++ b/.github/vale/config/vocabularies/Backstage/accept.txt
@@ -268,6 +268,7 @@ maintainer's
maintainership
makefile
Matomo
+matchers
md
memcache
memoization
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7552898bd0..36b4114efe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -203,7 +203,7 @@ jobs:
env:
CI: true
- NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules
+ NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules
INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }}
INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }}
INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }}
diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml
index e799bcae00..08f349c7ec 100644
--- a/.github/workflows/deploy_microsite.yml
+++ b/.github/workflows/deploy_microsite.yml
@@ -329,7 +329,7 @@ jobs:
run: ls microsite/build && ls microsite/build/storybook && ls microsite/build/api/stable && ls microsite/build/api/next
- name: Deploy both microsite and storybook to gh-pages
- uses: JamesIves/github-pages-deploy-action@4a3abc783e1a24aeb44c16e869ad83caf6b4cc23 # v4.7.4
+ uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a # v4.7.6
with:
branch: gh-pages
folder: microsite/build
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 96cb3368be..077eb2d856 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -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@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6
+ uses: github/codeql-action/upload-sarif@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8
with:
sarif_file: results.sarif
diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml
index e17a229256..bdf93aa8a0 100644
--- a/.github/workflows/sync_patch-release.yml
+++ b/.github/workflows/sync_patch-release.yml
@@ -36,6 +36,12 @@ jobs:
fetch-tags: true
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+ - name: Use Node.js 22.x
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: 22.x
+ registry-url: https://registry.npmjs.org/ # Needed for auth
+
- name: Configure Git
run: |
git config --global user.email noreply@backstage.io
diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml
index 8b76338645..632d03c35d 100644
--- a/.github/workflows/sync_snyk-monitor.yml
+++ b/.github/workflows/sync_snyk-monitor.yml
@@ -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@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6
+ uses: github/codeql-action/upload-sarif@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8
with:
sarif_file: snyk.sarif
diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml
index d06c8ea270..5d976f8583 100644
--- a/.github/workflows/verify_codeql.yml
+++ b/.github/workflows/verify_codeql.yml
@@ -55,7 +55,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6
+ uses: github/codeql-action/init@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8
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@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6
+ uses: github/codeql-action/autobuild@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8
# ℹ️ 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@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6
+ uses: github/codeql-action/analyze@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8
diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml
index 933bbb0840..f63eb5d61b 100644
--- a/.github/workflows/verify_e2e-windows.yml
+++ b/.github/workflows/verify_e2e-windows.yml
@@ -28,7 +28,7 @@ jobs:
env:
CI: true
- NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules
+ NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules
name: E2E Windows ${{ matrix.node-version }}
steps:
diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml
index ae2ec477d3..f7de04772d 100644
--- a/.github/workflows/verify_windows.yml
+++ b/.github/workflows/verify_windows.yml
@@ -21,7 +21,7 @@ jobs:
env:
CI: true
- NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules
+ NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules
INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }}
INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }}
INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }}
diff --git a/.storybook/main.ts b/.storybook/main.ts
index 788df83ba9..5453627f34 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -1,7 +1,13 @@
-import type { StorybookConfig } from '@storybook/react-vite';
+import { defineMain } from '@storybook/react-vite/node';
+import { fileURLToPath } from 'node:url';
+import { createRequire } from 'node:module';
import { join, dirname, posix } from 'path';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const require = createRequire(import.meta.url);
+
const isChromatic = process.env.STORYBOOK_STORY_SET === 'chromatic';
// All stories for full development
@@ -35,13 +41,14 @@ function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
-const config: StorybookConfig = {
+export default defineMain({
stories,
addons: [
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-themes'),
getAbsolutePath('@storybook/addon-docs'),
getAbsolutePath('@storybook/addon-a11y'),
+ getAbsolutePath('@storybook/addon-vitest'),
],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
@@ -122,6 +129,4 @@ const config: StorybookConfig = {
return config;
},
-};
-
-export default config;
+});
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 8f987ebab1..4e15677aed 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -1,8 +1,12 @@
-import React, { useEffect } from 'react';
+import addonA11y from '@storybook/addon-a11y';
+import addonDocs from '@storybook/addon-docs';
+import addonThemes from '@storybook/addon-themes';
+import addonLinks from '@storybook/addon-links';
+import { definePreview } from '@storybook/react-vite';
+import { useEffect } from 'react';
import { TestApiProvider } from '@backstage/test-utils';
import { Content, AlertDisplay } from '@backstage/core-components';
import { apis } from './support/apis';
-import type { Decorator, Preview } from '@storybook/react-vite';
import { useGlobals } from 'storybook/preview-api';
import { UnifiedThemeProvider, themes } from '@backstage/theme';
import { allModes } from './modes';
@@ -16,7 +20,7 @@ import './storybook.css';
// Custom themes
import './themes/spotify.css';
-const preview: Preview = {
+export default definePreview({
globalTypes: {
themeMode: {
name: 'Theme Mode',
@@ -47,10 +51,12 @@ const preview: Preview = {
},
},
},
+
initialGlobals: {
themeMode: 'light',
themeName: 'backstage',
},
+
parameters: {
layout: 'fullscreen',
@@ -72,7 +78,7 @@ const preview: Preview = {
},
viewport: {
- viewports: {
+ options: {
initial: {
name: 'Initial',
styles: { width: '320px', height: '100%' },
@@ -102,6 +108,7 @@ const preview: Preview = {
},
},
},
+
decorators: [
Story => {
const [globals] = useGlobals();
@@ -121,10 +128,11 @@ const preview: Preview = {
};
}, [selectedTheme, selectedThemeName]);
- document.body.style.backgroundColor = 'var(--bui-bg)';
+ document.body.style.backgroundColor = 'var(--bui-bg-surface-0)';
const docsStoryElements = document.getElementsByClassName('docs-story');
Array.from(docsStoryElements).forEach(element => {
- (element as HTMLElement).style.backgroundColor = 'var(--bui-bg)';
+ (element as HTMLElement).style.backgroundColor =
+ 'var(--bui-bg-surface-0)';
});
return (
@@ -140,6 +148,6 @@ const preview: Preview = {
);
},
],
-};
-export default preview;
+ addons: [addonLinks(), addonThemes(), addonDocs(), addonA11y()],
+});
diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css
index bcb1ded9ff..db3a0ca16c 100644
--- a/.storybook/themes/spotify.css
+++ b/.storybook/themes/spotify.css
@@ -216,9 +216,6 @@
}
[data-theme-mode='light'][data-theme-name='spotify'] {
- --bui-bg: var(--bui-gray-1);
- --bui-bg-surface-1: var(--bui-white);
- --bui-bg-surface-2: var(--bui-gray-2);
--bui-bg-solid: #1ed760;
--bui-bg-solid-hover: #3be477;
--bui-bg-solid-pressed: #1abc54;
@@ -248,9 +245,7 @@
}
[data-theme-mode='dark'][data-theme-name='spotify'] {
- --bui-bg: var(--bui-black);
- --bui-bg-surface-1: var(--bui-gray-1);
- --bui-bg-surface-2: var(--bui-gray-2);
+ --bui-bg-surface-0: var(--bui-black);
--bui-bg-solid: #1ed760;
--bui-bg-solid-hover: #3be477;
--bui-bg-solid-pressed: #1abc54;
diff --git a/OWNERS.md b/OWNERS.md
index 44b4ebca15..764237d9c1 100644
--- a/OWNERS.md
+++ b/OWNERS.md
@@ -5,11 +5,13 @@
Team: @backstage/maintainers
-| Maintainer | Organization | GitHub | Discord |
-| --------------- | ------------ | ----------------------------------------------- | ------------- |
-| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` |
-| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` |
-| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` |
+| Maintainer | Organization | GitHub | Discord |
+| --------------- | ------------ | ----------------------------------------------------- | ------------- |
+| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
+| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` |
+| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` |
+| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` |
+| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` |
## Project Areas
@@ -78,7 +80,7 @@ Scope: The Backstage Documentation and Microsite, excluding the plugins listing
| Name | Organization | GitHub | Discord |
| --------------- | ------------- | ----------------------------------------------------- | ------------- |
-| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
+| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` |
| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` |
@@ -267,6 +269,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling.
| Niklas Gustavsson | Spotify | [protocol7](https://github.com/protocol7) | |
| Dave Zolotusky | Spotify | [dzolotusky](https://github.com/dzolotusky) | |
| Pia Nilsson | Spotify | [pianilsson](https://github.com/pianilsson) | |
+| Stefan Särne | Spotify | [ssarne](https://github.com/ssarne) | |
## Organization Members
@@ -274,9 +277,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling.
| ------------------------------ | ------------------------- | ----------------------------------------------------- | ------------------------------ |
| Adam Harvey | Okta | [adamdmharvey](https://github.com/adamdmharvey) | `adamharvey_` |
| Alex Crome | | [afscrome](https://github.com/afscrome) | `afscrome` |
-| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
| Andrew Thauer | Wealthsimple | [andrewthauer](https://github.com/andrewthauer) | `andrewthauer#3060` |
-| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` |
| Brian Fletcher | Roadie.io | [punkle](https://github.com/punkle) | `Brian Fletcher#7051` |
| Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` |
| David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` |
diff --git a/app-config.yaml b/app-config.yaml
index c1a894dcc7..54ece44da3 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -109,8 +109,12 @@ integrations:
token: ${GITLAB_TOKEN}
### Example for how to add a bitbucket cloud integration
# bitbucketCloud:
+ # # Using API token
# - username: ${BITBUCKET_USERNAME}
# token: ${BITBUCKET_API_TOKEN}
+ # # Using OAuth
+ # - clientId: ${BITBUCKET_CLIENT_ID}
+ # clientSecret: ${BITBUCKET_CLIENT_SECRET}
### Example for how to add your bitbucket server instance using the API:
# - host: server.bitbucket.com
# apiBaseUrl: server.bitbucket.com
@@ -311,3 +315,8 @@ auth:
permission:
enabled: true
+
+devTools:
+ scheduledTasks:
+ plugins:
+ - catalog
diff --git a/docs-ui/package.json b/docs-ui/package.json
index ff5d59e7f0..2f17987107 100644
--- a/docs-ui/package.json
+++ b/docs-ui/package.json
@@ -32,13 +32,13 @@
"clsx": "^2.1.1",
"html-react-parser": "^5.2.5",
"motion": "^12.4.1",
- "next": "15.5.7",
+ "next": "15.5.9",
"next-mdx-remote-client": "^2.1.2",
"prop-types": "^15.8.1",
"react": "19.1.1",
"react-dom": "19.1.1",
"shiki": "^3.13.0",
- "storybook": "^8.6.12"
+ "storybook": "^8.6.15"
},
"devDependencies": {
"@octokit/rest": "^22.0.1",
diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css
index 59a5b986be..9b59448c11 100644
--- a/docs-ui/public/theme-backstage.css
+++ b/docs-ui/public/theme-backstage.css
@@ -1 +1 @@
-@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#ebebeb;--bui-bg-tint:transparent;--bui-bg-tint-hover:#1f549366;--bui-bg-tint-pressed:#1f549399;--bui-bg-tint-disabled:#ebebeb;--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#9c9c9c;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg:#333;--bui-bg-surface-1:#424242;--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#222;--bui-bg-tint:transparent;--bui-bg-tint-hover:#9cc9ff1f;--bui-bg-tint-pressed:#9cc9ff29;--bui-bg-tint-disabled:transparent;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:var(--bui-gray-5);--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}}
\ No newline at end of file
+@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#ebebeb;--bui-bg-tint:transparent;--bui-bg-tint-hover:#1f549366;--bui-bg-tint-pressed:#1f549399;--bui-bg-tint-disabled:#ebebeb;--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#9c9c9c;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#222;--bui-bg-tint:transparent;--bui-bg-tint-hover:#9cc9ff1f;--bui-bg-tint-pressed:#9cc9ff29;--bui-bg-tint-disabled:transparent;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:var(--bui-gray-5);--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}}
\ No newline at end of file
diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css
index b0bf517701..0f2ecc6081 100644
--- a/docs-ui/public/theme-spotify.css
+++ b/docs-ui/public/theme-spotify.css
@@ -1 +1 @@
-@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:var(--bui-gray-2);--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg:var(--bui-black);--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-tint:#242424;--bui-bg-tint-hover:#202020;--bui-bg-tint-pressed:#292929;--bui-bg-tint-disabled:#fffc;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#083618;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3}
\ No newline at end of file
+@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:var(--bui-gray-2);--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg-surface-0:var(--bui-black);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-tint:#242424;--bui-bg-tint-hover:#202020;--bui-bg-tint-pressed:#292929;--bui-bg-tint-disabled:#fffc;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#083618;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3}
\ No newline at end of file
diff --git a/docs-ui/src/app/tokens/page.mdx b/docs-ui/src/app/tokens/page.mdx
index 8e9031c81c..97266afbf4 100644
--- a/docs-ui/src/app/tokens/page.mdx
+++ b/docs-ui/src/app/tokens/page.mdx
@@ -187,7 +187,7 @@ These colors are used for special purposes like ring, scrollbar, ...
## Core background colors
These colors are used for the background of your application. We are mostly using for now a
-single elevated background for panels. `--bui-bg` should mostly use as the main background
+single elevated background for panels. `--bui-bg-surface-0` should mostly use as the main background
color of your app.
@@ -200,7 +200,7 @@ color of your app.
- --bui-bg
+ --bui-bg-surface-0The background color of your Backstage instance.
diff --git a/docs-ui/src/components/Snippet/styles.module.css b/docs-ui/src/components/Snippet/styles.module.css
index 3141564293..ed5bd22def 100644
--- a/docs-ui/src/components/Snippet/styles.module.css
+++ b/docs-ui/src/components/Snippet/styles.module.css
@@ -7,7 +7,7 @@
.preview {
border-radius: 8px;
box-shadow: inset 0 0 0 1px var(--border);
- background-color: var(--bui-bg);
+ background-color: var(--bui-bg-surface-0);
padding: 1px;
position: relative;
}
diff --git a/docs-ui/src/snippets/code-snippets.ts b/docs-ui/src/snippets/code-snippets.ts
index 1a22a24340..57169ae32a 100644
--- a/docs-ui/src/snippets/code-snippets.ts
+++ b/docs-ui/src/snippets/code-snippets.ts
@@ -4,7 +4,7 @@ export const customTheme = `:root {
--bui-font-regular: system-ui;
--bui-font-weight-regular: 400;
--bui-font-weight-bold: 600;
- --bui-bg: #f8f8f8;
+ --bui-bg-surface-0: #f8f8f8;
--bui-bg-surface-1: #fff;
/* ... other CSS variables */
@@ -19,7 +19,7 @@ export const customTheme = `:root {
--bui-font-regular: system-ui;
--bui-font-weight-regular: 400;
--bui-font-weight-bold: 600;
- --bui-bg: #f8f8f8;
+ --bui-bg-surface-0: #f8f8f8;
--bui-bg-surface-1: #fff;
/* ... other CSS variables */
diff --git a/docs-ui/yarn.lock b/docs-ui/yarn.lock
index 3634aca929..755b88ec09 100644
--- a/docs-ui/yarn.lock
+++ b/docs-ui/yarn.lock
@@ -854,10 +854,10 @@ __metadata:
languageName: node
linkType: hard
-"@next/env@npm:15.5.7":
- version: 15.5.7
- resolution: "@next/env@npm:15.5.7"
- checksum: 10/11f971691018bd62a5bf253fc843fb2a6cf1431468f5c3a9d4d41753a6ff3e8bf7f539f46aba3f58f8bac59e681bf05fb5d771ac08d7dbd966a601257e1368bf
+"@next/env@npm:15.5.9":
+ version: 15.5.9
+ resolution: "@next/env@npm:15.5.9"
+ checksum: 10/962329701343c2617c85ae99ef35adfd9e8f7b58d9c0316f2f0857f82875a91ef3151ef0743c50964b0066aa3d1214c5193cf229f7757d7c9329f7fb95605a4a
languageName: node
linkType: hard
@@ -1240,11 +1240,11 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/core@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/core@npm:8.6.14"
+"@storybook/core@npm:8.6.15":
+ version: 8.6.15
+ resolution: "@storybook/core@npm:8.6.15"
dependencies:
- "@storybook/theming": "npm:8.6.14"
+ "@storybook/theming": "npm:8.6.15"
better-opn: "npm:^3.0.2"
browser-assert: "npm:^1.2.1"
esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
@@ -1260,7 +1260,7 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
- checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f
+ checksum: 10/d268d6fa00c38b35e5c363ee33779c2e087ab8e4681e0e205baa2fdb2780ea9feda3c9f6db35d60092778878d2782b2093c744bdf1af173c5688c3e1e0e960ac
languageName: node
linkType: hard
@@ -1334,6 +1334,15 @@ __metadata:
languageName: node
linkType: hard
+"@storybook/theming@npm:8.6.15":
+ version: 8.6.15
+ resolution: "@storybook/theming@npm:8.6.15"
+ peerDependencies:
+ storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
+ checksum: 10/f02760831a13d7af9dbfeb6feea949f4c13c897861cbc75253a6776d133891567889c8d99c7e91a99124d0772c3bde1f978984c83b19117d1d7c908ed7eb8409
+ languageName: node
+ linkType: hard
+
"@swc/helpers@npm:0.5.15":
version: 0.5.15
resolution: "@swc/helpers@npm:0.5.15"
@@ -1416,12 +1425,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:^20":
- version: 20.19.11
- resolution: "@types/node@npm:20.19.11"
+"@types/node@npm:^22.13.14":
+ version: 22.19.2
+ resolution: "@types/node@npm:22.19.2"
dependencies:
undici-types: "npm:~6.21.0"
- checksum: 10/7179ed464a781cad20117362df58c12839b40922b53356e1a74f86c99d9d1ef9a27edce360901608f896c247c9c4131ba9c7222331e92cde17b723c75d5c8f2e
+ checksum: 10/c7e72fde4aef120d984416f4aa01d23b0704eeed7a0a63cdf6178783361f3985abb02a65133b80f47226ad61f38a27fe82c3cd6dd6a62d5b82bc643fe54b9c97
languageName: node
linkType: hard
@@ -1618,8 +1627,8 @@ __metadata:
linkType: hard
"@uiw/codemirror-themes@npm:^4.23.7":
- version: 4.25.3
- resolution: "@uiw/codemirror-themes@npm:4.25.3"
+ version: 4.25.4
+ resolution: "@uiw/codemirror-themes@npm:4.25.4"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
@@ -1628,7 +1637,7 @@ __metadata:
"@codemirror/language": ">=6.0.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
- checksum: 10/f808ac3c4763f623ed6ea6215cc1421bb87234e0a05e7b6d9e917ab304fc6a4977c238996ca538e22093af8136c02bb27512aeeac653cb8fe82b4917ba1668a3
+ checksum: 10/aab62e2c16a10dbde4592cef704d386c1a9e3caece4ff0eed85e9d9a5acdeaf3fd8717e2cb6a6795e12e890b8600956effb52d453aacbc618934e7ae8c3e59e3
languageName: node
linkType: hard
@@ -2188,9 +2197,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579":
- version: 1.0.30001735
- resolution: "caniuse-lite@npm:1.0.30001735"
- checksum: 10/4b404ed363a5ccf3ec07144c2360106aa47dcec1f896ea6705a2773c6977e9ffb19a2b58be9492462733170e29b4823211533b0986d5a25f0f65f7ff702470ab
+ version: 1.0.30001760
+ resolution: "caniuse-lite@npm:1.0.30001760"
+ checksum: 10/ac5c13d00b946c3ace331d25379ed9d85743b1028aba79ae80402cb128b4b491122585144898fefb836d4d9879c13c717a081ae241a00420cbbc7e1b934ec685
languageName: node
linkType: hard
@@ -2521,7 +2530,7 @@ __metadata:
"@shikijs/transformers": "npm:^3.13.0"
"@storybook/react": "npm:^8.6.12"
"@types/mdx": "npm:^2.0.13"
- "@types/node": "npm:^20"
+ "@types/node": "npm:^22.13.14"
"@types/react": "npm:19.1.9"
"@types/react-dom": "npm:19.1.7"
"@uiw/codemirror-themes": "npm:^4.23.7"
@@ -2533,13 +2542,13 @@ __metadata:
html-react-parser: "npm:^5.2.5"
lightningcss: "npm:^1.28.2"
motion: "npm:^12.4.1"
- next: "npm:15.5.7"
+ next: "npm:15.5.9"
next-mdx-remote-client: "npm:^2.1.2"
prop-types: "npm:^15.8.1"
react: "npm:19.1.1"
react-dom: "npm:19.1.1"
shiki: "npm:^3.13.0"
- storybook: "npm:^8.6.12"
+ storybook: "npm:^8.6.15"
typescript: "npm:^5"
unified: "npm:^11.0.4"
languageName: unknown
@@ -3451,9 +3460,9 @@ __metadata:
languageName: node
linkType: hard
-"framer-motion@npm:^12.23.25":
- version: 12.23.25
- resolution: "framer-motion@npm:12.23.25"
+"framer-motion@npm:^12.23.26":
+ version: 12.23.26
+ resolution: "framer-motion@npm:12.23.26"
dependencies:
motion-dom: "npm:^12.23.23"
motion-utils: "npm:^12.23.6"
@@ -3469,7 +3478,7 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: 10/0b55dc0a5319cd923cdddcb42726352f0d74a8160f5222a3f090af53bbb2ed0d93c8482da41c9ffa875a3f7f666586e43be7594dca3612e70a1dea6bfbefebb4
+ checksum: 10/29d51ac57ce8c2599664bf706d7a83c2aba4fe1dee1c0c7d75d5c45c120b1c2ca5d257c3ff608992b26bf8e9cf258cacf231b0873d7b16f01c5eeb42b9831f78
languageName: node
linkType: hard
@@ -5265,10 +5274,10 @@ __metadata:
linkType: hard
"motion@npm:^12.4.1":
- version: 12.23.25
- resolution: "motion@npm:12.23.25"
+ version: 12.23.26
+ resolution: "motion@npm:12.23.26"
dependencies:
- framer-motion: "npm:^12.23.25"
+ framer-motion: "npm:^12.23.26"
tslib: "npm:^2.4.0"
peerDependencies:
"@emotion/is-prop-valid": "*"
@@ -5281,7 +5290,7 @@ __metadata:
optional: true
react-dom:
optional: true
- checksum: 10/67e86dd3c695362812fd86d06e82bb53173900212a78802c94adbcff947e70316141ec6e2908fb3ebf70d3686578bb7fbe7a820a359ce553cd2d79ff77fc1428
+ checksum: 10/c5e683d2c67e55816587cbbd451c5600dc0602aa04108ab0bf8d4472befd490e90b3177362dbc8b9f96316ec16383924cc574eb7c9e031540f1e88e64a2704ae
languageName: node
linkType: hard
@@ -5342,11 +5351,11 @@ __metadata:
languageName: node
linkType: hard
-"next@npm:15.5.7":
- version: 15.5.7
- resolution: "next@npm:15.5.7"
+"next@npm:15.5.9":
+ version: 15.5.9
+ resolution: "next@npm:15.5.9"
dependencies:
- "@next/env": "npm:15.5.7"
+ "@next/env": "npm:15.5.9"
"@next/swc-darwin-arm64": "npm:15.5.7"
"@next/swc-darwin-x64": "npm:15.5.7"
"@next/swc-linux-arm64-gnu": "npm:15.5.7"
@@ -5397,7 +5406,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
- checksum: 10/bfac0cbac41b36227ec91d3a0727561f73dfc35d6a78eafd0200528ef95fa2e9d2dba5a8c8922864b4f4e4321ae6a07c6cf8f5766ac3192ad03e68516c3a458a
+ checksum: 10/ac27b82de08c9720e8e99cd64102af5e30306a8c861630d50da347a02c288cc441273ada64875c47e7a4d5991ff69cc92b23e906ce3271f9835a7911a0f060cc
languageName: node
linkType: hard
@@ -6473,11 +6482,11 @@ __metadata:
languageName: node
linkType: hard
-"storybook@npm:^8.6.12":
- version: 8.6.14
- resolution: "storybook@npm:8.6.14"
+"storybook@npm:^8.6.15":
+ version: 8.6.15
+ resolution: "storybook@npm:8.6.15"
dependencies:
- "@storybook/core": "npm:8.6.14"
+ "@storybook/core": "npm:8.6.15"
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
@@ -6487,7 +6496,7 @@ __metadata:
getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs
storybook: ./bin/index.cjs
- checksum: 10/104932fe29ebf49bef24c90285741cec964d1c36b3f0b38da1dace31ac664be457f2a510e41ba69fe8aa0d90c20e3446fec27d1ced41e5eefb3a9ca713d99e79
+ checksum: 10/15762c79ec8444a46bc14cddfadbdd54dfd379828acd38555887a246c01e7c9ebb61e4eafafe04efb3ddf6278fb47035216e7f7d9f94fc205da148870173abdf
languageName: node
linkType: hard
diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md
index 3e60124775..8d882227ec 100644
--- a/docs/api/utility-apis.md
+++ b/docs/api/utility-apis.md
@@ -10,31 +10,31 @@ Backstage plugins strive to be self-contained, with as much functionality as pos
Backstage provides two primary methods for plugins to communicate across their
boundaries in client-side code. The first one being the
-[`createPlugin`](../reference/core-plugin-api.createplugin.md) API along with the
+[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) API along with the
extensions that it can provide, and the second one being Utility APIs. While the
-[`createPlugin`](../reference/core-plugin-api.createplugin.md) API is focused on
+[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) API is focused on
the initialization plugins and the app, the Utility APIs provide ways for
plugins to communicate during their entire life cycle.
## Consuming APIs
-Each Utility API is tied to an [`ApiRef`](../reference/frontend-plugin-api.apiref.md)
+Each Utility API is tied to an [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)
instance, which is a global singleton object without any additional state or
functionality, its only purpose is to reference Utility APIs.
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md)s are created using
-[`createApiRef`](../reference/frontend-plugin-api.createapiref.md), which is exported
-by [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). There are also
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s are created using
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), which is exported
+by [`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). There are also
many predefined Utility APIs in
-[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), and they're all
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), and they're all
exported with a name of the pattern `*ApiRef`, for example
-[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md).
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html).
To access one of the Utility APIs inside a React component, use the
-[`useApi`](../reference/frontend-plugin-api.useapi.md) hook exported by
-[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), or the
-[`withApis`](../reference/frontend-plugin-api.withapis.md) HOC if you prefer class
+[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) hook exported by
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), or the
+[`withApis`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.withApis.html) HOC if you prefer class
components. For example, the
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md) can be accessed like this:
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) can be accessed like this:
```tsx
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
@@ -52,14 +52,14 @@ export const MyComponent = () => {
```
Note that there is no explicit type given for
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md). This is because the
-[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) has the type
-embedded, and [`useApi`](../reference/frontend-plugin-api.useapi.md) is able to infer
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). This is because the
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) has the type
+embedded, and [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) is able to infer
the type.
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
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). The only
requirement is that they are beneath the `AppProvider` in the react tree.
## Supplying APIs
@@ -67,15 +67,15 @@ requirement is that they are beneath the `AppProvider` in the react tree.
### API Factories
APIs are registered in the form of
-[`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) instances, which encapsulate
+[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) instances, which encapsulate
the process of instantiating an API. It is a collection of three things: the
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md) of the API to instantiate, a
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API to instantiate, a
list of all required dependencies, and a factory function that returns a new API
instance.
For example, this is the default
-[`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) for the
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md):
+[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) for the
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html):
```ts
createApiFactory({
@@ -89,25 +89,25 @@ createApiFactory({
});
```
-In this example, the [`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md)
+In this example, the [`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html)
is our API, which encapsulates the
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md) type. The
-[`alertApiRef`](../reference/frontend-plugin-api.alertapiref.md) is our single
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) type. The
+[`alertApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.alertApiRef.html) is our single
dependency, which we give the name `alertApi`, and is then passed on to the
factory function, which returns an implementation of the
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md).
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html).
-The [`createApiFactory`](../reference/frontend-plugin-api.createapifactory.md)
+The [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html)
function is a thin wrapper that enables TypeScript type inference. You may
notice that there are no type annotations in the above example, and that is
because we're able to infer all types from the
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md)s. TypeScript will make sure
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s. TypeScript will make sure
that the return value of the `factory` function matches the type embedded in
-`api`'s [`ApiRef`](../reference/frontend-plugin-api.apiref.md), in this case the
-[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md). It will also match the
+`api`'s [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), in this case the
+[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). It will also match the
types between the `deps` and the parameters of the `factory` function, again
using the type embedded within the
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md)s.
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s.
## Registering API Factories
@@ -119,13 +119,13 @@ app, and the app itself.
Starting with the Backstage core library, it provides implementations for all of
the core APIs. The core APIs are the ones exported by
-[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), such as the
-[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) and
-[`configApiRef`](../reference/frontend-plugin-api.configapiref.md).
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), such as the
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) and
+[`configApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.configApiRef.html).
The core APIs are loaded for any app created with
-[`createApp`](../reference/app-defaults.createapp.md) from
-[`@backstage/core-plugin-api`](../reference/app-defaults.md), which means that
+[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html) from
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), which means that
there is no step that needs to be taken to include these APIs in an app.
### Plugin APIs
@@ -133,13 +133,13 @@ there is no step that needs to be taken to include these APIs in an app.
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
-supplies a default [`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) of
+supplies a default [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) 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
will cause the app to refuse to start.
Plugins supply their APIs through the `apis` option of
-[`createPlugin`](../reference/core-plugin-api.createplugin.md), for example:
+[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html), for example:
```ts
export const techdocsPlugin = createPlugin({
@@ -164,7 +164,7 @@ 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
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`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html) implementation and
therefore, not possible to override.
Overriding APIs is useful for apps that want to switch out behavior to tailor it
@@ -227,33 +227,33 @@ const app = createApp({
```
Note that the above line will cause an error if `IgnoreErrorApi` does not fully
-implement the [`ErrorApi`](../reference/frontend-plugin-api.errorapi.md), as it is
+implement the [`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html), as it is
checked by the type embedded in the
-[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) at compile time.
+[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) at compile time.
## Defining custom Utility APIs
Plugins are free to define their own Utility APIs. Simply define the TypeScript
interface for the API and create an
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md) using
-[`createApiRef`](../reference/frontend-plugin-api.createapiref.md) exported from
-[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also, be sure to
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) using
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) exported from
+[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). 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).
+for the API in [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html).
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/frontend-plugin-api.apiref.md) of the API, and any associated
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API, and any associated
types. To make an API private, just avoid exporting the
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md), but still be sure to supply a
-default factory to [`createPlugin`](../reference/core-plugin-api.createplugin.md).
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), but still be sure to supply a
+default factory to [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html).
Private APIs are useful for plugins that want to depend on other APIs outside of
React components, but not have to expose an entire API surface to maintain. When
using private APIs, it is fine to use the `typeof` of an implementing class as
the type parameter passed to
-[`createApiRef`](../reference/frontend-plugin-api.createapiref.md), while public APIs
+[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), while public APIs
should always define a separate TypeScript interface type.
Plugins may depend on APIs from other plugins, both in React components and as
@@ -262,13 +262,13 @@ dependencies between plugins.
## Architecture
-The [`ApiRef`](../reference/frontend-plugin-api.apiref.md) instances mentioned above
+The [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) instances mentioned above
provide a point of indirection between consumers and producers of Utility APIs.
It allows for plugins and components to depend on APIs in a type-safe way,
without having a direct reference to a concrete implementation of the APIs. The
Apps are also given a lot of flexibility in what implementations to provide. As
long as they adhere to the contract established by an
-[`ApiRef`](../reference/frontend-plugin-api.apiref.md), they are free to choose any
+[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), they are free to choose any
implementation they want.
The figure below shows the relationship between
@@ -291,16 +291,16 @@ The indirection provided by Utility APIs also makes it straightforward to test
components that depend on APIs, and to provide a standard common development
environment for plugins. A proper test wrapper with mocked API implementations
is not yet ready, but it will be provided as a part of
-[`@backstage/test-utils`](../reference/test-utils.md). It will provide mocked
+[`@backstage/test-utils`](https://backstage.io/api/stable/modules/_backstage_test-utils.html). It will provide mocked
variants of APIs, with additional methods for asserting a component's
interaction with the API.
The common development environment for plugins is included in
-[`@backstage/dev-utils`](../reference/dev-utils.md), where the exported
-[`createDevApp`](../reference/dev-utils.createdevapp.md) function creates an
+[`@backstage/dev-utils`](https://backstage.io/api/stable/modules/_backstage_dev-utils.html), where the exported
+[`createDevApp`](https://backstage.io/api/stable/functions/_backstage_dev-utils.createDevApp.html) function creates an
application with implementations for all core APIs already present. Contrary to
the method for wiring up Utility API implementations in an app created with
-[`createApp`](../reference/app-defaults.createapp.md),
-[`createDevApp`](../reference/dev-utils.createdevapp.md) uses automatic dependency
+[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html),
+[`createDevApp`](https://backstage.io/api/stable/functions/_backstage_dev-utils.createDevApp.html) uses automatic dependency
injection. This is to make it possible to replace any API implementation, and
having that be reflected in dependents of that API.
diff --git a/docs/auth/index.md b/docs/auth/index.md
index bff1f3df26..1d668f6961 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -340,7 +340,7 @@ The method with which frontend plugins request access to third-party services is
through [Utility APIs](../api/utility-apis.md) for each service provider. These
are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a
full list of providers, see the
-[@backstage/core-plugin-api](../reference/core-plugin-api.md#variables) reference.
+[@backstage/core-plugin-api](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.index.html#alertapiref) reference.
## Custom Authentication Provider
@@ -459,6 +459,42 @@ providerFactories: {
},
```
+In the new backend system you can leverage the `authProvidersExtensionPoint` for this:
+
+```ts
+// your-auth-plugin-module.ts
+export const gheAuth = createBackendModule({
+ // This ID must be exactly "auth" because that's the plugin it targets
+ pluginId: 'auth',
+ // This ID must be unique, but can be anything
+ moduleId: 'ghe-auth-provider',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ providers: authProvidersExtensionPoint,
+ logger: coreServices.logger,
+ },
+ async init({ providers, logger }) {
+ providers.registerProvider({
+ // This ID must match the actual provider config, e.g. addressing
+ // auth.providers.ghe means that this must be "ghe".
+ providerId: 'ghe',
+ factory: createOAuthProviderFactory({
+ authenticator: githubAuthenticator,
+ signInResolverFactories: {
+ ...commonSignInResolvers,
+ },
+ }),
+ });
+ },
+ });
+ },
+});
+
+// backend index.ts
+backend.add(gheAuth);
+```
+
## Configuring token issuers
By default, the Backstage authentication backend generates and manages its own signing keys automatically for any issued
diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md
index 7c4b09d9bf..55827ab1dc 100644
--- a/docs/auth/oidc.md
+++ b/docs/auth/oidc.md
@@ -241,6 +241,12 @@ These parameters have implicit default values. Don't override them unless you kn
- `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the
user has no active session.
- `sessionDuration`: Lifespan of the user session.
+- `startUrlSearchParams`: This is a dictionary of search (query) parameters for the OIDC
+ authorization start URL. Don't define it unless you want to change the identity
+ provider's behavior. (For example, you could set the `organization` parameter to guide
+ users towards a particular sign-in option that your organization prefers.) **Note:** the
+ start URL is controlled by the browser, so this feature is only for improving the
+ Backstage user experience.
:::note Config Reloading
Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage.
diff --git a/docs/backend-system/core-services/cache.md b/docs/backend-system/core-services/cache.md
index ad4d04b50a..a23332409b 100644
--- a/docs/backend-system/core-services/cache.md
+++ b/docs/backend-system/core-services/cache.md
@@ -11,24 +11,84 @@ This service lets your plugin interact with a cache. It is bound to your plugin
The cache service can be configured using the `backend.cache` section in your `app-config.yaml`:
+### In-Memory (default)
+
```yaml
backend:
cache:
- store: redis # or 'valkey', 'memcache', 'memory'
+ store: memory
+```
+
+### Memcache
+
+```yaml
+backend:
+ cache:
+ store: memcache
+ connection: user:pass@cache.example.com:11211
+```
+
+### Redis
+
+```yaml
+backend:
+ cache:
+ store: redis
connection: redis://localhost:6379
- # Store-specific configuration (Redis/Valkey only)
+ # Store-specific configuration (optional)
redis:
client:
- # Optional: Global namespace prefix for all cache keys
+ # Global namespace prefix for all cache keys
namespace: 'my-app'
- # Optional: Separator used between namespace and plugin ID (default: ':')
+ # Separator used between namespace and plugin ID (default: ':')
keyPrefixSeparator: ':'
# Other Redis-specific options...
clearBatchSize: 1000
useUnlink: false
```
+### Valkey
+
+```yaml
+backend:
+ cache:
+ store: valkey
+ connection: redis://localhost:6379
+
+ # Store-specific configuration (optional)
+ valkey:
+ # Global namespace prefix for all cache keys (including separator used between namespace and plugin ID)
+ keyPrefix: 'my-app:'
+```
+
+### Infinispan
+
+```yaml
+backend:
+ cache:
+ store: infinispan
+
+ # Store-specific configuration (optional)
+ infinispan:
+ servers:
+ # IP address or hostname of the server (default: '127.0.0.1')
+ - host: 127.0.0.1
+ # Port number of the server (default: '11222')
+ port: 11222
+ # Name of the cache (default: 'cache')
+ cacheName: cache
+ mediaType: application/json
+ authentication:
+ # Whether authentication is enabled (default: 'false')
+ enabled: true
+ userName: yourusername
+ password: yourpassword
+ saslMechanism: PLAIN
+```
+
+A full list of configuration items is available [here](https://docs.jboss.org/infinispan/hotrod-clients/javascript/1.0/apidocs/module-infinispan.html), including support for backup clusters.
+
### Namespace Configuration
For Redis and Valkey stores, you can configure a global namespace that will be prefixed to all cache keys:
@@ -36,9 +96,10 @@ For Redis and Valkey stores, you can configure a global namespace that will be p
- **Without namespace**: Cache keys use only the plugin ID (e.g., `catalog:some-key`)
- **With namespace**: Cache keys use the format `namespace:pluginId:key` (e.g., `my-app:catalog:some-key`)
-The `keyPrefixSeparator` controls what character is used between the namespace and plugin ID (defaults to `:`).
+For **Redis**, `keyPrefixSeparator` controls what character is used between the namespace and plugin ID (defaults to `:`).
+For **Valkey**, you set the full `keyPrefix` including the separator.
-**Note**: Memory and Memcache stores do not support namespace configuration and will always use the plugin ID directly.
+**Note**: In-memory, Memcache and Infinispan stores do not support namespace configuration and will always use the plugin ID directly.
## Using the service
diff --git a/docs/conf/reading.md b/docs/conf/reading.md
index a565f3a8a3..b6119b0da9 100644
--- a/docs/conf/reading.md
+++ b/docs/conf/reading.md
@@ -7,7 +7,7 @@ description: Documentation on Reading Backstage Configuration
## Config API
There's a common configuration API for by both frontend and backend plugins. An
-API reference can be found [here](../reference/config.config.md).
+API reference can be found [here](https://backstage.io/api/stable/types/_backstage_config.Config.html).
The configuration API is tailored towards failing fast in case of missing or bad
config. That's because configuration errors can always be considered programming
@@ -115,7 +115,7 @@ example `getString`. These will throw an error if there is no value available.
## Accessing ConfigApi in Frontend Plugins
-The [ConfigApi](../reference/frontend-plugin-api.configapi.md) in the frontend is a
+The [ConfigApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ConfigApi.html) in the frontend is a
[UtilityApi](../api/utility-apis.md). It's accessible as usual via the
`configApiRef` exported from `@backstage/core-plugin-api`:
diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md
index 81acfdf720..e0fb645d78 100644
--- a/docs/conf/user-interface/index.md
+++ b/docs/conf/user-interface/index.md
@@ -103,13 +103,13 @@ Backstage UI is using light by default under `:root` but you can target it more
[data-theme-mode='light'] {
/* Light theme specific styles */
- --bui-bg: #f8f8f8;
+ --bui-bg-surface-0: #f8f8f8;
--bui-fg-primary: #000;
}
[data-theme-mode='dark'] {
/* Dark theme specific styles */
- --bui-bg: #333333;
+ --bui-bg-surface-0: #333333;
--bui-fg-primary: #fff;
}
```
@@ -122,18 +122,18 @@ We recommend starting with a core set of CSS variables to quickly achieve a bran
And if you’d like to go even further, you can target specific component class names for advanced customization.
-| Token Name | Description |
-| -------------------- | ----------------------------------------------------------------------------------- |
-| `--bui-bg` | This is used to define the background color of your app. It will only be used once. |
-| `--bui-bg-surface-1` | We ar using this color to sit on top of `--bui-bg` mostly for `Card`, `Dialog`, ... |
-| `--bui-bg-surface-2` | This is for content inside elevated components. This colour is less common. |
-| `--bui-bg-solid` | This is used for main actions like primary buttons. |
-| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. |
-| `--bui-fg-primary` | Your primary text or icon colours. |
-| `--bui-fg-secondary` | Your secondary text or icon colours. |
-| `--bui-fg-link` | Used for links. |
-| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... |
-| `--bui-font-regular` | The main font of your app. |
+| Token Name | Description |
+| -------------------- | --------------------------------------------------------------------------------------------- |
+| `--bui-bg-surface-0` | This is used to define the background color of your app. It will only be used once. |
+| `--bui-bg-surface-1` | We ar using this color to sit on top of `--bui-bg-surface-0` mostly for `Card`, `Dialog`, ... |
+| `--bui-bg-surface-2` | This is for content inside elevated components. This colour is less common. |
+| `--bui-bg-solid` | This is used for main actions like primary buttons. |
+| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. |
+| `--bui-fg-primary` | Your primary text or icon colours. |
+| `--bui-fg-secondary` | Your secondary text or icon colours. |
+| `--bui-fg-link` | Used for links. |
+| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... |
+| `--bui-font-regular` | The main font of your app. |
All available CSS variables
@@ -157,13 +157,14 @@ These colors are used for special purposes like ring, scrollbar, ...
#### Core background colors
-These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. `--bui-bg` should mostly use as the main background color of your app.
+These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. `--bui-bg-surface-0` should mostly use as the main background color of your app.
| Token Name | Description |
| ------------------------- | ------------------------------------------------ |
-| `--bui-bg` | The background color of your Backstage instance. |
+| `--bui-bg-surface-0` | The background color of your Backstage instance. |
| `--bui-bg-surface-1` | Use for any panels or elevated surfaces. |
| `--bui-bg-surface-2` | Use for any panels or elevated surfaces. |
+| `--bui-bg-surface-3` | Use for any panels or elevated surfaces. |
| `--bui-bg-solid` | Used for solid background colors. |
| `--bui-bg-solid-hover` | Used for solid background colors when hovered. |
| `--bui-bg-solid-pressed` | Used for solid background colors when pressed. |
diff --git a/docs/deployment/scaling.md b/docs/deployment/scaling.md
index 2c3b687f67..dd765d3c1a 100644
--- a/docs/deployment/scaling.md
+++ b/docs/deployment/scaling.md
@@ -17,7 +17,7 @@ into multiple different services, each running a different set of plugins. This
is a more advanced approach and requires you to be able to route requests to
the appropriate backends based on the plugin ID. Both for ingress, but also
internal traffic between Backstage backends, which is done by creating a custom
-implementation of the [DiscoveryService](../reference/backend-plugin-api.discoveryservice.md) interface. See the [backend system docs](../backend-system/building-backends/01-index.md#split-into-multiple-backends) for more details on how to separate your deployment into multiple backend instances.
+implementation of the [DiscoveryService](https://backstage.io/api/stable/interfaces/_backstage_backend-plugin-api.index.DiscoveryService.html) interface. See the [backend system docs](../backend-system/building-backends/01-index.md#split-into-multiple-backends) for more details on how to separate your deployment into multiple backend instances.
Lastly, you can also replicate the Backstage deployments across multiple regions.
This is not a pattern that there is built-in support for and typically only makes
diff --git a/docs/features/software-templates/ui-options-examples.md b/docs/features/software-templates/ui-options-examples.md
index 1a024569a6..845da8e60b 100644
--- a/docs/features/software-templates/ui-options-examples.md
+++ b/docs/features/software-templates/ui-options-examples.md
@@ -468,3 +468,59 @@ repoUrl:
The supported `additionalScopes` values are `gerrit`, `github`, `gitlab`, `bitbucket`, and `azure`.
If you're also using the `RepoUrlPicker` field extension, you should simply duplicate this part from there.
+
+## RepoOwnerPicker
+
+The input props that can be specified under `ui:options` for the `RepoOwnerPicker` field extension.
+
+### `host`
+
+The SCM integration host that owners should be fetched from for autocompletion.
+
+- Fetch owners from `github.com`
+
+```yaml
+repoUrl:
+ title: Repository Owner
+ type: string
+ ui:field: RepoOwnerPicker
+ ui:options:
+ host: github.com
+```
+
+### `excludedOwners`
+
+List of owners that should be excluded from autocompletion.
+
+- Exclude owner `owner_1` from autocompletion
+
+```yaml
+repoUrl:
+ title: Repository Owner
+ type: string
+ ui:field: RepoOwnerPicker
+ ui:options:
+ excludedOwners:
+ - owner_1
+```
+
+### `requestUserCredentials`
+
+If defined will request user credentials to auth against the given SCM platform.
+
+```yaml
+repoUrl:
+ title: Repository Owner
+ type: string
+ ui:field: RepoOwnerPicker
+ ui:options:
+ requestUserCredentials:
+ secretsKey: USER_OAUTH_TOKEN
+ additionalScopes:
+ github:
+ - workflow:write
+```
+
+`secretsKey` is the key used within the template secrets context to store the credential and `additionalScopes` is any additional permission scopes to request.
+
+The supported `additionalScopes` values are `gerrit`, `github`, `gitlab`, `bitbucket`, and `azure`.
diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md
index 32f17f945f..5d9fcba603 100644
--- a/docs/features/software-templates/writing-templates.md
+++ b/docs/features/software-templates/writing-templates.md
@@ -555,6 +555,35 @@ The `RepoBranchPicker` is a custom field that we provide part of the
`plugin-scaffolder`. You can provide your own custom fields by
[writing your own Custom Field Extensions](./writing-custom-field-extensions.md)
+### The Repository Owner Picker
+
+Similar to the repository picker, there is a picker for owners to support autocompletion. A full example could look like this:
+
+```yaml
+- title: Choose an owner
+ required:
+ - repoOwner
+ properties:
+ repoOwner:
+ title: Repository Owner
+ type: string
+ ui:field: RepoOwnerPicker
+ ui:options:
+ host: github.com
+ excludedOwners:
+ - backstage
+ requestUserCredentials:
+ secretsKey: USER_OAUTH_TOKEN
+```
+
+Passing the `requestUserCredentials` and `host` properties is required for autocompletion to work. For more information regarding the `requestUserCredentials` object, please refer to the [Using the Users `oauth` token](#using-the-users-oauth-token) section under [The Repository Picker](#the-repository-picker).
+
+For a list of all possible `ui:options` input props for `RepoOwnerPicker`, please visit [here](./ui-options-examples.md#repoownerpicker).
+
+The `RepoOwnerPicker` is a custom field that we provide part of the
+`plugin-scaffolder`. You can provide your own custom fields by
+[writing your own Custom Field Extensions](./writing-custom-field-extensions.md)
+
### Accessing the signed-in users details
Sometimes when authoring templates, you'll want to access the user that is running the template, and get details from the profile or the users `Entity` in the Catalog.
diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md
index 283c162fa7..fd88478b37 100644
--- a/docs/frontend-system/building-plugins/01-index.md
+++ b/docs/frontend-system/building-plugins/01-index.md
@@ -220,6 +220,6 @@ export const examplePlugin = createFrontendPlugin({
});
```
-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).
+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](https://backstage.io/api/stable/modules/_backstage_plugin-catalog-react.index.html).
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/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
index 71989e898f..abac1f03f5 100644
--- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
+++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md
@@ -11,35 +11,35 @@ This section covers many of the [extension blueprints](../architecture/23-extens
These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage frontend framework itself.
-### Api - [Reference](../../reference/frontend-plugin-api.apiblueprint.md)
+### Api - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ApiBlueprint.html)
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.
-### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint.md)
+### NavItem - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavItemBlueprint.html)
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.pageblueprint.md)
+### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PageBlueprint.html)
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.signinpageblueprint.md)
+### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SignInPageBlueprint.html)
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.
-### SwappableComponent - [Reference](../../reference/frontend-plugin-api.swappablecomponentblueprint.md)
+### SwappableComponent - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SwappableComponentBlueprint.html)
Swappable Components are extensions that are used to replace the implementations of components in the app and plugins.
-### Theme - [Reference](../../reference/frontend-plugin-api.themeblueprint.md)
+### Theme - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ThemeBlueprint.html)
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.
-### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md)
+### Icons - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.IconBundleBlueprint.html)
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.translationblueprint.md)
+### Translation - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.TranslationBlueprint.html)
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.
diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md
index e0fab954ac..3f5920be5d 100644
--- a/docs/integrations/bitbucketCloud/discovery.md
+++ b/docs/integrations/bitbucketCloud/discovery.md
@@ -54,6 +54,8 @@ You need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Further documentation:
@@ -72,6 +74,8 @@ Additionally, you need to decide how you want to receive events from external so
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Set up your provider
diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md
index 6b735171a4..807076c9e9 100644
--- a/docs/integrations/bitbucketCloud/locations.md
+++ b/docs/integrations/bitbucketCloud/locations.md
@@ -32,6 +32,15 @@ integrations:
appPassword: my-password
```
+OAuth 2.0 client credentials flow:
+
+```yaml
+integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+```
+
:::note Note
A public Bitbucket Cloud provider is added automatically at startup for
@@ -41,7 +50,7 @@ convenience, so you only need to list it if you want to supply credentials.
:::note Note
-The credential required for this type is either an [Api token](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/) or an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work.
+The credential required for this type is either an [Api token](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/), an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) or an [OAuth 2.0 client credentials](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/). An Atlassian Account API key will not work.
:::
@@ -55,3 +64,5 @@ This one entry will have the following elements:
neither a username nor token are supplied, anonymous access will be used.
- `token`: The token used to authenticate requests.
- `appPassword`: The app password for the Bitbucket Cloud user.
+- `clientId`: The OAuth client ID for Bitbucket Cloud (used with `clientSecret` for OAuth 2.0 client credentials flow).
+- `clientSecret`: The OAuth client secret for Bitbucket Cloud (used with `clientId` for OAuth 2.0 client credentials flow).
diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md
index 350bc33aed..bbdd105af4 100644
--- a/docs/integrations/bitbucketServer/discovery.md
+++ b/docs/integrations/bitbucketServer/discovery.md
@@ -42,6 +42,8 @@ You need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Further documentation:
diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md
index f245e960ea..e17f514058 100644
--- a/docs/integrations/github/discovery.md
+++ b/docs/integrations/github/discovery.md
@@ -141,7 +141,7 @@ events:
region: us-east-2
```
-The [AWS SQS module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md#configuration) has more details on the configuration options, the example above includes on the required options.
+The [AWS SQS module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md#configuration) has more details on the configuration options, the example above includes only the required options.
### Events Setup using Google Pub/Sub module
@@ -179,7 +179,53 @@ events:
targetTopic: 'github.{{ event.attributes.x-github-event }}'
```
-The [Google Pub/Sub module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-google-pubsub/README.md#configuration) has more details on the configuration options, the example above includes on the required options.
+The [Google Pub/Sub module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-google-pubsub/README.md#configuration) has more details on the configuration options, the example above includes only the required options.
+
+### Events Setup using Kafka module
+
+Alternatively to using the HTTP endpoint you can use the Kafka module, here's how.
+
+First we need to add the package:
+
+```bash title="from your Backstage root directory"
+yarn --cwd packages/backend add @backstage/plugin-events-backend-module-kafka
+```
+
+Then we need to add it to your backend:
+
+```ts title="in packages/backend/src/index.ts"
+backend.add(import('@backstage/plugin-events-backend'));
+backend.add(import('@backstage/plugin-events-backend-module-github'));
+/* highlight-add-start */
+backend.add(import('@backstage/plugin-events-backend-module-kafka'));
+/* highlight-add-end */
+```
+
+Finally you will want to configure it:
+
+```yaml title="app-config.yaml
+events:
+ modules:
+ kafka:
+ kafkaConsumingEventPublisher:
+ # Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ clientId: your-client-id
+ # List of brokers in the Kafka cluster to connect to.
+ brokers:
+ - broker1
+ - broker2
+ topics:
+ # Replace with actual topic name as expected by subscribers
+ - topic: 'backstage.topic'
+ kafka:
+ # The Kafka topics to subscribe to.
+ topics:
+ - topic1
+ # The GroupId to be used by the topic consumers.
+ groupId: your-group-id
+```
+
+The [Kafka module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-kafka/README.md#configuration) has more details on the configuration options, the example above includes only the required options.
## Configuration
diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md
index 284451af8c..2dac8b8866 100644
--- a/docs/integrations/github/org.md
+++ b/docs/integrations/github/org.md
@@ -132,6 +132,8 @@ You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
The webhook will need to be configured to forward `organization`,`team` and `membership` events.
diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md
index 5e02c853fa..3d47edba6d 100644
--- a/docs/integrations/gitlab/discovery.md
+++ b/docs/integrations/gitlab/discovery.md
@@ -43,6 +43,8 @@ You need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md#configuration)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Further documentation:
@@ -92,6 +94,8 @@ Additionally, you need to decide how you want to receive events from external so
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Set up your provider
diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md
index 8249a97c24..2908bb7c09 100644
--- a/docs/integrations/gitlab/org.md
+++ b/docs/integrations/gitlab/org.md
@@ -49,6 +49,8 @@ You need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md#configuration)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Further documentation:
@@ -101,6 +103,8 @@ Additionally, you need to decide how you want to receive events from external so
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
+- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md)
+- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md)
Set up your provider
diff --git a/docs/notifications/processors.md b/docs/notifications/processors.md
index c25b669ed6..af0b9f8f93 100644
--- a/docs/notifications/processors.md
+++ b/docs/notifications/processors.md
@@ -149,11 +149,63 @@ notifications:
broadcastChannels: # Optional, if you wish to support broadcast notifications.
- C12345678
username: 'Backstage Bot' # Optional, defaults to the name of the Slack App.
+ concurrencyLimit: 20 # Optional, number of messages allowed per interval. Defaults to 10.
+ throttleInterval: 1m # Optional, Accepts ISO-8601 duration, ms-style ("1m", "30s"), or HumanDuration ({ minutes: 2 }). Defaults to 1 minute
```
Multiple instances can be added in the `slack` array, allowing you to have multiple configurations if you need to send
messages to more than one Slack workspace. Org-Wide App installation is not currently supported.
+### Broadcast Channel Routing
+
+For more granular control over where broadcast notifications are sent, you can use `broadcastRoutes` to route notifications to different Slack channels based on their origin and/or topic. This is useful when you want different types of notifications to go to different channels.
+
+```yaml
+notifications:
+ processors:
+ slack:
+ - token: xoxb-XXXXXXXXX
+ # Legacy option - used as fallback when no routes match
+ broadcastChannels:
+ - general-notifications
+ # Route broadcasts based on origin and/or topic
+ broadcastRoutes:
+ # Most specific: matches both origin AND topic
+ - origin: plugin:catalog
+ topic: alerts
+ channel: catalog-alerts
+ # Origin only: all notifications from this origin
+ - origin: plugin:catalog
+ channel: catalog-updates
+ # Topic only: all notifications with this topic (any origin)
+ - topic: security
+ channel: security-team
+ # Multiple channels: send to several channels at once
+ - origin: external:monitoring
+ channel:
+ - ops-team
+ - on-call-alerts
+```
+
+#### Route Matching Precedence
+
+Routes are evaluated in the following order of priority:
+
+1. **Origin + Topic match** (most specific) - A route that specifies both `origin` and `topic` will match first
+2. **Origin-only match** - A route with only `origin` specified (no `topic`)
+3. **Topic-only match** - A route with only `topic` specified (no `origin`)
+4. **Default fallback** - If no routes match, falls back to `broadcastChannels`
+
+The first matching route wins. If no routes match and no `broadcastChannels` are configured, the broadcast notification will not be sent to Slack.
+
+#### Configuration Options
+
+| Property | Type | Description |
+| --------- | ---------------------- | ------------------------------------------------------------------------------------------ |
+| `origin` | `string` | Optional. The notification origin to match (e.g., `plugin:catalog`, `external:my-service`) |
+| `topic` | `string` | Optional. The notification topic to match (e.g., `alerts`, `updates`, `security`) |
+| `channel` | `string` or `string[]` | Required. The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs |
+
### Entity Requirements
Entities must be annotated with the following annotation:
diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md
index ed2e25c601..3542e073cb 100644
--- a/docs/plugins/integrating-plugin-into-software-catalog.md
+++ b/docs/plugins/integrating-plugin-into-software-catalog.md
@@ -32,7 +32,7 @@ Creating the plugin...
### Reading entities from within your plugin
You can access the currently selected entity using the backstage api
-[`useEntity`](../reference/plugin-catalog-react.useentity.md). For example,
+[`useEntity`](https://backstage.io/api/stable/functions/_backstage_plugin-catalog-react.index.useEntity.html). For example,
```tsx
import { useEntity } from '@backstage/plugin-catalog-react';
diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md
index 0d18942320..7cfaca4561 100644
--- a/docs/plugins/structure-of-a-plugin.md
+++ b/docs/plugins/structure-of-a-plugin.md
@@ -81,8 +81,8 @@ export const ExamplePage = examplePlugin.provide(
```
This is where the plugin is created and where it creates and exports extensions
-that can be imported and used the app. See reference docs for
-[`createPlugin`](../reference/core-plugin-api.createplugin.md) or introduction to
+that can be imported and used in the app. See reference docs for
+[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) or introduction to
the new [Composability System](./composability.md).
## Components
diff --git a/docs/releases/v1.46.0-changelog.md b/docs/releases/v1.46.0-changelog.md
new file mode 100644
index 0000000000..22e6e39dab
--- /dev/null
+++ b/docs/releases/v1.46.0-changelog.md
@@ -0,0 +1,2369 @@
+# Release v1.46.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0](https://backstage.github.io/upgrade-helper/?to=1.46.0)
+
+## @backstage/plugin-techdocs-addons-test-utils@2.0.0
+
+### Major Changes
+
+- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value.
+
+ Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used.
+
+ One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead.
+
+ ```ts
+ import { screen } from 'shadow-dom-testing-library';
+
+ // ... render the addon ...
+ await TechDocsAddonTester.buildAddonsInTechDocs([])
+ .withDom(TEST_CONTENT)
+ .renderWithEffects();
+
+ expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument();
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/test-utils@1.7.14
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## @backstage/backend-app-api@1.4.0
+
+### Minor Changes
+
+- 5a2d538: Introduced backend startup result tracking and error handling. The `Backend.start()` method now returns a `BackendStartupResult` with detailed success/failure status and timing information for all plugins and modules. When startup fails, a `BackendStartupError` is thrown that includes the complete startup results, making it easier to diagnose which plugins or modules failed.
+
+ This also improves the default error message when backend startup fails, and of course makes it possible to craft your own custom error reporting based on the startup results.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/backend-defaults@0.14.0
+
+### Minor Changes
+
+- fa43826: Move `better-sqlite3` from dependencies to peer dependencies
+- 2bc4e02: **BREAKING** The correct configuration options for Valkey are now being used.
+
+ These changes are **required** to `app-config.yaml`:
+
+ ```diff
+ backend:
+ cache:
+ store: valkey
+ connection: ...
+ client:
+ - namespace: 'my-app'
+ - keyPrefixSeparator: ':'
+ + keyPrefix: 'my-app:'
+ - clearBatchSize: 1000
+ - useUnlink: false
+ ```
+
+ In comparison to Redis, Valkey requires the full `keyPrefix` including the separator to be specified instead of separate `namespace` and `keyPrefixSeparator` options. Also, Valkey does not support the `clearBatchSize` and `useUnlink` options.
+
+### Patch Changes
+
+- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+ **BREAKING CHANGES**
+
+ - **@backstage/integration** (`src/bitbucketCloud/core.ts`)
+
+ - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited.
+
+ - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`)
+ - `getBitbucketClient` now returns a `Promise` and **must** be awaited.
+ - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited.
+
+ **OAuth usage example**
+
+ ```yaml
+ integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+ ```
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+
+- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2
+
+- f96edff: Allow configuration of the `referrerPolicy`
+
+- fb029b6: Updated luxon types
+
+- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`.
+
+- 847a330: Fix for `jose` types
+
+- 25b560e: Internal change to support new versions of the `logform` library
+
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+
+- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`.
+
+- 42db6a6: Don't warn when parsing `storeOptions` for `memory` cache
+
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config-loader@1.10.7
+ - @backstage/backend-dev-utils@0.1.6
+
+## @backstage/backend-plugin-api@1.6.0
+
+### Minor Changes
+
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+
+### Patch Changes
+
+- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`.
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/cli-common@0.1.16
+
+## @backstage/cli@0.35.0
+
+### Minor Changes
+
+- f6f22a9: Provide `--no-node-snapshot` by default when running the `package start` or `package test`. You can disable this behavior by providing `NODE_OPTIONS='--node-snapshot'`.
+
+- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`.
+
+ You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages.
+
+ You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
+
+ A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
+
+- cd0b8a1: **BREAKING**: `jest` is now a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project.
+
+ You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed.
+
+ - **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations.
+ - **Jest 30**: Install `jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27`. Fixes the stylesheet parsing warnings/errors, but requires migration steps.
+
+ See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions.
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+
+- e7db290: Add missing peer/dev dependencies to the frontend plugin template.
+
+ `react-dom` was not declared as a peer dependency, causing module resolution
+ errors when generating plugins outside a Backstage monorepo. This adds
+ `react-dom` to `peerDependencies` (for consuming apps) and `devDependencies`
+ (for local development). `react-router-dom` is also added to `peerDependencies` (for consuming apps) and `devDependencies`
+ to support routing during plugin development.
+
+ Fixes:
+
+ - Module not found: Can't resolve 'react-dom'
+ - Module not found: Can't resolve 'react-router-dom'
+
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+
+- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`.
+
+- 2b81751: Updated dependency `webpack` to `~5.103.0`.
+
+- fafd9e1: Fixed internal usage of `yargs`.
+
+- c8c2329: Add proxy configuration from env-vars to create-app tasks
+
+- 2bae83a: Switched compilation target to ES2022 in order to match the new set of supported Node.js versions, which are 22 and 24.
+
+ The TypeScript compilation target has been set to ES2022, because setting it to a higher target will break projects on older TypeScript versions. If you use a newer TypeScript version in your own project, you can bump `compilerOptions.target` to ES2023 or ES2024 in your own `tsconfig.json` file.
+
+- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`.
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/integration@1.19.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+
+## @backstage/integration@1.19.0
+
+### Minor Changes
+
+- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+ **BREAKING CHANGES**
+
+ - **@backstage/integration** (`src/bitbucketCloud/core.ts`)
+
+ - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited.
+
+ - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`)
+ - `getBitbucketClient` now returns a `Promise` and **must** be awaited.
+ - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited.
+
+ **OAuth usage example**
+
+ ```yaml
+ integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+ ```
+
+### Patch Changes
+
+- a26a322: Added support for using a GitHub App installation to generate tokens for public repository access when the `publicAccess` option is enabled. When all other authentication methods fail (e.g., the app is not installed in that organization), the provider will now use an available installation to generate a token that can be used to access public repositories as read only.
+- fb029b6: Updated luxon types
+- e15fdae: Made the github urls case insensitive.
+
+## @backstage/ui@0.10.0
+
+### Minor Changes
+
+- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component.
+
+ ### Migration Guide
+
+ If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead:
+
+ **Before:**
+
+ ```tsx
+ }
+ href="/path"
+ />
+ ```
+
+ **After:**
+
+ ```tsx
+ }
+ href="/path"
+ />
+ ```
+
+ For custom cell content, use the new generic `Cell` component:
+
+ ```tsx
+ {/* Your custom content */}
+ ```
+
+### Patch Changes
+
+- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked.
+
+ Affected components: Checkbox
+
+- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element.
+
+ Affected components: ButtonIcon
+
+- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link.
+
+ Affected components: Row
+
+- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode.
+
+ Affected components: Table, TableHeader, Row, Column
+
+- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios.
+
+- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending.
+
+ Affected components: Column
+
+## @backstage/plugin-auth-backend-module-github-provider@0.4.0
+
+### Minor Changes
+
+- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-catalog-backend@3.3.0
+
+### Minor Changes
+
+- dce1824: Added `ActionsRegistry` actions for `register-entity` and `unregister-entity`
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 8101ec1: Fixed default refresh service to go through the whole ancestry of the entity.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-github@0.12.0
+
+### Minor Changes
+
+- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID.
+
+### Patch Changes
+
+- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances.
+
+ When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances.
+ Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail.
+
+- a413977: Added configurable `pageSizes` option to `GithubOrgEntityProvider` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. This aligns the configuration options with `GithubMultiOrgEntityProvider`.
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-events-backend-module-kafka@0.2.0
+
+### Minor Changes
+
+- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility.
+- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics.
+
+ This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-kubernetes-backend@0.21.0
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`,
+ as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme)
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-node@0.4.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-kubernetes-node@0.4.0
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+
+## @backstage/plugin-notifications-backend-module-slack@0.3.0
+
+### Minor Changes
+
+- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics.
+
+### Patch Changes
+
+- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options.
+- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## @backstage/plugin-scaffolder@1.35.0
+
+### Minor Changes
+
+- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners.
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## @backstage/plugin-scaffolder-backend@3.1.0
+
+### Minor Changes
+
+- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity.
+
+### Patch Changes
+
+- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Updated `isolated-vm` to `6.0.1`
+- 25b560e: Internal change to support new versions of the `logform` library
+- 8f4aded: Fixing OpenAPI definition
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+
+### Minor Changes
+
+- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+ **BREAKING CHANGES**
+
+ - **@backstage/integration** (`src/bitbucketCloud/core.ts`)
+
+ - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited.
+
+ - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`)
+ - `getBitbucketClient` now returns a `Promise` and **must** be awaited.
+ - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited.
+
+ **OAuth usage example**
+
+ ```yaml
+ integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0
+
+### Minor Changes
+
+- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-search-backend-node@1.4.0
+
+### Minor Changes
+
+- 4d3ddb9: Improving method that search tokenizer breaks apart entity names
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/app-defaults@1.7.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/backend-dev-utils@0.1.6
+
+### Patch Changes
+
+- 2bae83a: Internal update for Node.js v24 support.
+
+## @backstage/backend-dynamic-feature-service@0.7.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-app-node@0.1.40
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-events-backend@0.5.9
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/backend-openapi-utils@0.6.4
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/backend-test-utils@1.10.2
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 8be23a4: Switched `textextensions` dependency for `text-extensions`.
+- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/cli-common@0.1.16
+
+### Patch Changes
+
+- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way.
+- c8c2329: Add proxy configuration from env-vars to create-app tasks
+- 2bae83a: Bumped dev dependencies `@types/node`
+
+## @backstage/cli-node@0.2.16
+
+### Patch Changes
+
+- 4e8c726: Updated to use new utilities from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @backstage/codemods@0.1.53
+
+### Patch Changes
+
+- 688f070: Updated to use new utilities from `@backstage/cli-common`.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @backstage/config-loader@1.10.7
+
+### Patch Changes
+
+- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @backstage/core-app-api@1.19.3
+
+### Patch Changes
+
+- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription.
+- 97cd16f: Internal update of translation imports.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/core-compat-api@0.5.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/core-components@0.18.4
+
+### Patch Changes
+
+- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection.
+
+ In addition, improved the `LogViewer` component by a few usability enhancements:
+
+ - Added support for multiple selections using cmd/ctrl + click
+ - Improved the generated hash that is added to the URL to also support ranges & multiple selections
+ - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality
+ - Added some color and a separator to the line numbers to improve readability
+
+- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container
+
+- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip
+
+- 5d52dab: Add i18n support for LogViewer search control
+
+- f6b49ce: added support for wrapLongLines option in CodeSnippet
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+
+## @backstage/core-plugin-api@1.12.1
+
+### Patch Changes
+
+- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs.
+- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+
+## @backstage/create-app@0.7.7
+
+### Patch Changes
+
+- 336db00: Bumped create-app version.
+- 2bae83a: Updated engines to support Node 22 or 24
+- c8c2329: Add proxy configuration from env-vars to create-app tasks
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @backstage/dev-utils@1.1.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/ui@0.10.0
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/app-defaults@1.7.3
+ - @backstage/integration-react@1.2.13
+
+## @backstage/frontend-app-api@0.13.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-defaults@0.3.4
+
+## @backstage/frontend-defaults@0.3.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.3.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/frontend-app-api@0.13.3
+
+## @backstage/frontend-dynamic-feature-loader@0.1.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+
+## @backstage/frontend-plugin-api@0.13.2
+
+### Patch Changes
+
+- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries.
+
+ The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode.
+
+- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error.
+
+- f3f84f1: Made the return type of `.withOverrides` to be simplified.
+
+- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old.
+
+- 9b8bde4: Removed unnecessary dependencies on `@backstage/core-components`, `@backstage/config`, `@material-ui/core`, and `lodash`.
+
+## @backstage/frontend-test-utils@0.4.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-app@0.3.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/test-utils@1.7.14
+ - @backstage/frontend-app-api@0.13.3
+
+## @backstage/integration-react@1.2.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/repo-tools@0.16.1
+
+### Patch Changes
+
+- 688f070: Updated to use new utilities from `@backstage/cli-common`.
+
+- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting.
+
+ - Updates modelGeneric.mustache templates in both client and server generators
+ - Updates modelTaggedUnion.mustache templates in both client and server generators
+ - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility
+ - Maintains backward compatibility - no changes when propertyNaming=original is not used
+
+- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions.
+
+- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+
+## @techdocs/cli@1.10.3
+
+### Patch Changes
+
+- 43629b1: Updated to use new utilities from `@backstage/cli-common`.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-techdocs-node@1.13.10
+ - @backstage/cli-common@0.1.16
+
+## @backstage/test-utils@1.7.14
+
+### Patch Changes
+
+- 97cd16f: Internal update of translation imports.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/theme@0.7.1
+
+### Patch Changes
+
+- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme.
+
+## @backstage/plugin-api-docs@0.13.2
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-app@0.3.3
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-app-backend@0.5.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-app-node@0.1.40
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config-loader@1.10.7
+
+## @backstage/plugin-app-node@0.1.40
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config-loader@1.10.7
+
+## @backstage/plugin-app-visualizer@0.1.26
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/ui@0.10.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-auth@0.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/theme@0.7.1
+
+## @backstage/plugin-auth-backend@0.25.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.15
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-node@0.6.10
+
+### Patch Changes
+
+- 2389358: remove leading dot in auth cookie cleanup call
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- b35f8b2: Fixed chunked cookie replacing edge case in OAuthCookieManager class where some of the old chunks would not get removed if new chunked cookie would have fewer chunks.
+- e9dd634: fix flawed cookie removal logic with chunked tokens
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-auth-react@0.1.22
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.5
+
+### Patch Changes
+
+- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+ **BREAKING CHANGES**
+
+ - **@backstage/integration** (`src/bitbucketCloud/core.ts`)
+
+ - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited.
+
+ - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`)
+ - `getBitbucketClient` now returns a `Promise` and **must** be awaited.
+ - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited.
+
+ **OAuth usage example**
+
+ ```yaml
+ integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+ ```
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+
+## @backstage/plugin-catalog@1.32.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.17
+
+### Patch Changes
+
+- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances.
+
+ When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances.
+ Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail.
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-catalog-backend-module-github@0.12.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.7.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.7.6
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.8.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-scaffolder-common@1.7.4
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-graph@0.5.4
+
+### Patch Changes
+
+- a67c144: Fixed zoom out description (replace `&` with &)
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-catalog-import@0.13.8
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-catalog-node@1.20.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-catalog-react@1.21.4
+
+### Patch Changes
+
+- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters.
+- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions.
+- c51c901: $contains may have string value in an entity filter. Typescript type and Zod parser were not the same.
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/frontend-test-utils@0.4.2
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.24
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+
+- df4d646: Moved types, API and client to the common package, allowing both frontend and
+ backend plugins to use the `CatalogUnprocessedEntitiesClient`.
+
+ The following types, clients and interfaces have been deprecated and should be
+ imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead:
+ `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`,
+ `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`.
+
+ All those types, clients and interfaces are re-exported temporarily in the
+ `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up.
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12
+
+## @backstage/plugin-catalog-unprocessed-entities-common@0.0.12
+
+### Patch Changes
+
+- df4d646: Moved types, API and client to the common package, allowing both frontend and
+ backend plugins to use the `CatalogUnprocessedEntitiesClient`.
+
+ The following types, clients and interfaces have been deprecated and should be
+ imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead:
+ `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`,
+ `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`.
+
+ All those types, clients and interfaces are re-exported temporarily in the
+ `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up.
+
+## @backstage/plugin-config-schema@0.1.75
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-devtools@0.1.34
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-devtools-common@0.1.20
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-devtools-backend@0.5.12
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+ - @backstage/plugin-devtools-common@0.1.20
+
+## @backstage/plugin-devtools-common@0.1.20
+
+### Patch Changes
+
+- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin
+
+## @backstage/plugin-events-backend@0.5.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-azure@0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-github@0.4.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-events-backend-test-utils@0.1.51
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+
+## @backstage/plugin-events-node@0.4.18
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-gateway-backend@1.1.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 7e860dd: Updated dependency `eventsource` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-home@0.8.15
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-home-react@0.1.33
+ - @backstage/theme@0.7.1
+
+## @backstage/plugin-home-react@0.1.33
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-kubernetes@0.12.14
+
+### Patch Changes
+
+- f15d5f1: add missing i18n support for kubernetes and kubernetes-react
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-kubernetes-react@0.5.14
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-kubernetes-cluster@0.0.32
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/plugin-kubernetes-react@0.5.14
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## @backstage/plugin-kubernetes-common@0.9.9
+
+### Patch Changes
+
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+
+## @backstage/plugin-kubernetes-react@0.5.14
+
+### Patch Changes
+
+- f966a85: Enabled a pod terminal at GKE
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+- f15d5f1: add missing i18n support for kubernetes and kubernetes-react
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-mcp-actions-backend@0.1.6
+
+### Patch Changes
+
+- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-mui-to-bui@0.2.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.10.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+
+## @backstage/plugin-notifications@0.5.12
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+
+- 53347cc: Move long notification descriptions behind `Show more/less` button.
+
+ This improves readability of the notifications list by preventing long descriptions from taking up too much space
+ or rendering very small scrollable areas.
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-signals-react@0.0.18
+
+## @backstage/plugin-notifications-backend@0.6.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## @backstage/plugin-notifications-backend-module-email@0.3.17
+
+### Patch Changes
+
+- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update.
+
+ The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes:
+
+ ```diff
+ notifications:
+ processors:
+ email:
+ transportConfig:
+ transport: "ses"
+ region: "us-west-2"
+ sender: "sender@mycompany.com"
+ replyTo: "no-reply@mycompany.com"
+ sesConfig:
+ - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com"
+ fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com"
+ ```
+
+- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`.
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## @backstage/plugin-notifications-node@0.2.22
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-org@0.6.47
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-org-react@0.1.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-permission-backend@0.7.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-permission-node@0.10.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-permission-react@0.4.39
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-proxy-backend@0.6.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-proxy-node@0.1.11
+
+## @backstage/plugin-proxy-node@0.1.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+
+### Patch Changes
+
+- 5a6aca2: Improve error message when provided target branch is missing
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.3
+
+### Patch Changes
+
+- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-notifications-node@0.2.22
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.16
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.6
+
+## @backstage/plugin-scaffolder-common@1.7.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+
+## @backstage/plugin-scaffolder-node@0.12.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-common@1.7.4
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.10.2
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-react@1.19.4
+
+### Patch Changes
+
+- fb029b6: Updated luxon types
+- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- 9b38f22: Updated dependency `use-immer` to `^0.11.0`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder-common@1.7.4
+
+## @backstage/plugin-search@1.5.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-search-backend@2.0.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-search-backend-module-catalog@0.3.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.7.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-search-backend-module-explore@0.3.10
+
+### Patch Changes
+
+- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`.
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-search-backend-module-pg@0.5.51
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-techdocs-node@1.13.10
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-search-react@1.10.1
+
+### Patch Changes
+
+- 8947a4e: Skip the very first empty search when going to the landing page
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+
+## @backstage/plugin-signals@0.0.26
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-signals-react@0.0.18
+
+## @backstage/plugin-signals-backend@0.3.11
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-signals-node@0.1.27
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-signals-react@0.0.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-techdocs@1.16.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- 5c33821: Fixed schema type for additionalAllowedURIProtocols
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- 592361e: The `techdocs` config is now marked as optional.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## @backstage/plugin-techdocs-backend@2.1.3
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types
+- 592361e: The `techdocs` config is now marked as optional.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-techdocs-node@1.13.10
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+
+### Patch Changes
+
+- 8d6709e: Updated tests to match test-utils change
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## @backstage/plugin-techdocs-node@1.13.10
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## @backstage/plugin-techdocs-react@1.3.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @backstage/plugin-user-settings@0.8.30
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-signals-react@0.0.18
+
+## @backstage/plugin-user-settings-backend@0.3.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+
+## example-app@0.2.116
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/cli@0.35.0
+ - @backstage/ui@0.10.0
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-user-settings@0.8.30
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-devtools@0.1.34
+ - @backstage/plugin-home@0.8.15
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/app-defaults@1.7.3
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-mui-to-bui@0.2.2
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## example-app-next@0.0.30
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/cli@0.35.0
+ - @backstage/ui@0.10.0
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-app@0.3.3
+ - @backstage/plugin-org@0.6.47
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-app-visualizer@0.1.26
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-user-settings@0.8.30
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-home@0.8.15
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/app-defaults@1.7.3
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/frontend-defaults@0.3.4
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-auth@0.1.3
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## app-next-example-plugin@0.0.30
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+
+## example-backend@0.0.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-mcp-actions-backend@0.1.6
+ - @backstage/plugin-scaffolder-backend@3.1.0
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-app-backend@0.5.9
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-devtools-backend@0.5.12
+ - @backstage/plugin-events-backend@0.5.9
+ - @backstage/plugin-kubernetes-backend@0.21.0
+ - @backstage/plugin-notifications-backend@0.6.1
+ - @backstage/plugin-permission-backend@0.7.7
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-proxy-backend@0.6.9
+ - @backstage/plugin-search-backend@2.0.9
+ - @backstage/plugin-signals-backend@0.3.11
+ - @backstage/plugin-techdocs-backend@2.1.3
+ - @backstage/plugin-auth-backend-module-github-provider@0.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-search-backend-module-explore@0.3.10
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17
+ - @backstage/plugin-search-backend-module-catalog@0.3.11
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9
+
+## e2e-test@0.2.35
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.7.7
+ - @backstage/cli-common@0.1.16
+
+## @internal/frontend@0.0.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+
+## @internal/scaffolder@0.0.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-scaffolder-react@1.19.4
+
+## techdocs-cli-embedded-app@0.2.115
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/cli@0.35.0
+ - @backstage/ui@0.10.0
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/core-components@0.18.4
+ - @backstage/test-utils@1.7.14
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/app-defaults@1.7.3
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## yarn-plugin-backstage@0.0.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @internal/plugin-todo-list@1.0.46
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## @internal/plugin-todo-list-backend@1.0.46
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
diff --git a/docs/releases/v1.46.0-next.2-changelog.md b/docs/releases/v1.46.0-next.2-changelog.md
new file mode 100644
index 0000000000..0bb2bf687d
--- /dev/null
+++ b/docs/releases/v1.46.0-next.2-changelog.md
@@ -0,0 +1,1897 @@
+# Release v1.46.0-next.2
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2)
+
+## @backstage/backend-plugin-api@1.6.0-next.1
+
+### Minor Changes
+
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/cli@0.35.0-next.2
+
+### Minor Changes
+
+- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`.
+
+ You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages.
+
+ You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
+
+ A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`.
+- 2b81751: Updated dependency `webpack` to `~5.103.0`.
+- fafd9e1: Fixed internal usage of `yargs`.
+- 2bae83a: Switched ECMAScript version to ES2023.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-kubernetes-backend@0.21.0-next.2
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/plugin-kubernetes-node@0.4.0-next.2
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-kubernetes-node@0.4.0-next.2
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1
+
+### Minor Changes
+
+- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/backend-app-api@1.4.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/backend-defaults@0.14.0-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2
+- f96edff: Allow configuration of the `referrerPolicy`
+- fb029b6: Updated luxon types
+- 847a330: Fix for `jose` types
+- 25b560e: Internal change to support new versions of the `logform` library
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`.
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/backend-dev-utils@0.1.6-next.0
+ - @backstage/backend-app-api@1.4.0-next.1
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+
+## @backstage/backend-dev-utils@0.1.6-next.0
+
+### Patch Changes
+
+- 2bae83a: Internal update for Node.js v24 support.
+
+## @backstage/backend-dynamic-feature-service@0.7.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-app-node@0.1.40-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-events-backend@0.5.9-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/backend-openapi-utils@0.6.4-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/backend-test-utils@1.10.2-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 8be23a4: Switched `textextensions` dependency for `text-extensions`.
+- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/backend-app-api@1.4.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/cli-common@0.1.16-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
+## @backstage/codemods@0.1.53-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16-next.2
+
+## @backstage/config-loader@1.10.7-next.1
+
+### Patch Changes
+
+- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/core-app-api@1.19.3-next.1
+
+### Patch Changes
+
+- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription.
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/core-components@0.18.4-next.2
+
+### Patch Changes
+
+- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/create-app@0.7.7-next.2
+
+### Patch Changes
+
+- 2bae83a: Updated engines to support Node 22 or 24
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16-next.2
+
+## @backstage/frontend-plugin-api@0.13.2-next.1
+
+### Patch Changes
+
+- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries.
+
+ The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode.
+
+- f3f84f1: Made the return type of `.withOverrides` to be simplified.
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/integration@1.18.3-next.1
+
+### Patch Changes
+
+- fb029b6: Updated luxon types
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/repo-tools@0.16.1-next.2
+
+### Patch Changes
+
+- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/errors@1.2.7
+
+## @techdocs/cli@1.10.3-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-techdocs-node@1.13.10-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+
+## @backstage/plugin-api-docs@0.13.2-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-react@0.4.39-next.0
+
+## @backstage/plugin-app@0.3.3-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.39-next.0
+
+## @backstage/plugin-app-backend@0.5.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-app-node@0.1.40-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-app-node@0.1.40-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+
+## @backstage/plugin-auth-backend@0.25.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.7-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.7-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+
+## @backstage/plugin-auth-node@0.6.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- e9dd634: fix flawed cookie removal logic with chunked tokens
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog@1.32.1-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.5-next.0
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+
+## @backstage/plugin-catalog-backend@3.2.1-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.12-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gitea@0.1.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-github@0.11.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-backend-module-github@0.11.3-next.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-catalog-backend-module-ldap@0.12.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.8.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-catalog-graph@0.5.4-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-catalog-node@1.20.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-catalog-react@1.21.4-next.2
+
+### Patch Changes
+
+- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-compat-api@0.5.5-next.0
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-test-utils@0.4.2-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-react@0.4.39-next.0
+
+## @backstage/plugin-devtools-backend@0.5.12-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-devtools-common@0.1.19
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-events-backend@0.5.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-azure@0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-events-backend-module-github@0.4.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-backend-module-kafka@0.1.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-events-node@0.4.18-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-gateway-backend@1.1.1-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-home@0.8.15-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home-react@0.1.33-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/theme@0.7.1-next.0
+
+## @backstage/plugin-home-react@0.1.33-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/core-plugin-api@1.12.1-next.0
+
+## @backstage/plugin-kubernetes-cluster@0.0.32-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+ - @backstage/plugin-kubernetes-react@0.5.14-next.1
+ - @backstage/plugin-permission-react@0.4.39-next.0
+
+## @backstage/plugin-mcp-actions-backend@0.1.6-next.1
+
+### Patch Changes
+
+- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-notifications-backend@0.6.1-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
+## @backstage/plugin-notifications-backend-module-email@0.3.17-next.1
+
+### Patch Changes
+
+- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
+## @backstage/plugin-notifications-backend-module-slack@0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
+## @backstage/plugin-notifications-node@0.2.22-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-notifications-common@0.2.0
+
+## @backstage/plugin-org@0.6.47-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+
+## @backstage/plugin-permission-backend@0.7.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-permission-node@0.10.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-proxy-backend@0.6.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-proxy-node@0.1.11-next.1
+
+## @backstage/plugin-proxy-node@0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
+## @backstage/plugin-scaffolder@1.34.4-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+
+## @backstage/plugin-scaffolder-backend@3.1.0-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Updated `isolated-vm` to `6.0.1`
+- 25b560e: Internal change to support new versions of the `logform` library
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1
+
+### Patch Changes
+
+- 5a6aca2: Improve error message when provided target branch is missing
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.16-next.1
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1
+
+## @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.10.2-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
+## @backstage/plugin-scaffolder-react@1.19.4-next.2
+
+### Patch Changes
+
+- fb029b6: Updated luxon types
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- 9b38f22: Updated dependency `use-immer` to `^0.11.0`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+
+## @backstage/plugin-search@1.5.1-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+
+## @backstage/plugin-search-backend@2.0.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-catalog@0.3.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-explore@0.3.10-next.1
+
+### Patch Changes
+
+- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-pg@0.5.51-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-techdocs-node@1.13.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-search-backend-node@1.4.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-signals-backend@0.3.11-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-signals-node@0.1.27-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
+## @backstage/plugin-techdocs@1.16.1-next.2
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/plugin-auth-react@0.1.22-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+
+## @backstage/plugin-techdocs-backend@2.1.3-next.2
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-techdocs-node@1.13.10-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
+## @backstage/plugin-techdocs-node@1.13.10-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality.
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-techdocs-common@0.1.1
+
+## @backstage/plugin-user-settings-backend@0.3.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## example-app@0.2.116-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/cli@0.35.0-next.2
+ - @backstage/plugin-catalog-graph@0.5.4-next.1
+ - @backstage/plugin-api-docs@0.13.2-next.1
+ - @backstage/plugin-techdocs@1.16.1-next.2
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/plugin-search@1.5.1-next.1
+ - @backstage/plugin-org@0.6.47-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home@0.8.15-next.1
+ - @backstage/plugin-scaffolder@1.34.4-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32-next.2
+ - @backstage/app-defaults@1.7.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/frontend-app-api@0.13.3-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/ui@0.10.0-next.1
+ - @backstage/plugin-auth-react@0.1.22-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-import@0.13.8-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1
+ - @backstage/plugin-devtools@0.1.34-next.0
+ - @backstage/plugin-kubernetes@0.12.14-next.1
+ - @backstage/plugin-mui-to-bui@0.2.2-next.1
+ - @backstage/plugin-notifications@0.5.12-next.0
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-signals@0.0.26-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+ - @backstage/plugin-user-settings@0.8.30-next.0
+
+## example-app-next@0.0.30-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/cli@0.35.0-next.2
+ - @backstage/plugin-catalog-graph@0.5.4-next.1
+ - @backstage/plugin-api-docs@0.13.2-next.1
+ - @backstage/plugin-techdocs@1.16.1-next.2
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/plugin-search@1.5.1-next.1
+ - @backstage/plugin-app@0.3.3-next.1
+ - @backstage/plugin-org@0.6.47-next.1
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home@0.8.15-next.1
+ - @backstage/plugin-scaffolder@1.34.4-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32-next.2
+ - @backstage/app-defaults@1.7.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-compat-api@0.5.5-next.0
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/frontend-app-api@0.13.3-next.0
+ - @backstage/frontend-defaults@0.3.4-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/ui@0.10.0-next.1
+ - @backstage/plugin-app-visualizer@0.1.26-next.1
+ - @backstage/plugin-auth@0.1.3-next.0
+ - @backstage/plugin-auth-react@0.1.22-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-import@0.13.8-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1
+ - @backstage/plugin-kubernetes@0.12.14-next.1
+ - @backstage/plugin-notifications@0.5.12-next.0
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-signals@0.0.26-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+ - @backstage/plugin-user-settings@0.8.30-next.0
+
+## example-backend@0.0.45-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-mcp-actions-backend@0.1.6-next.1
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-app-backend@0.5.9-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1
+ - @backstage/plugin-auth-backend@0.25.7-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-devtools-backend@0.5.12-next.1
+ - @backstage/plugin-events-backend@0.5.9-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.0-next.2
+ - @backstage/plugin-notifications-backend@0.6.1-next.1
+ - @backstage/plugin-permission-backend@0.7.7-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/plugin-proxy-backend@0.6.9-next.1
+ - @backstage/plugin-scaffolder-backend@3.1.0-next.1
+ - @backstage/plugin-search-backend@2.0.9-next.1
+ - @backstage/plugin-signals-backend@0.3.11-next.1
+ - @backstage/plugin-techdocs-backend@2.1.3-next.2
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.10-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.11-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+
+## techdocs-cli-embedded-app@0.2.115-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/cli@0.35.0-next.2
+ - @backstage/plugin-techdocs@1.16.1-next.2
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/app-defaults@1.7.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/test-utils@1.7.14-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/ui@0.10.0-next.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+
+## @internal/plugin-todo-list-backend@1.0.46-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
diff --git a/docs/releases/v1.46.0.md b/docs/releases/v1.46.0.md
new file mode 100644
index 0000000000..9ee4c68681
--- /dev/null
+++ b/docs/releases/v1.46.0.md
@@ -0,0 +1,129 @@
+---
+id: v1.46.0
+title: v1.46.0
+description: Backstage Release v1.46.0
+---
+
+These are the release notes for the v1.46.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
+
+To begin with, this release has a few important framework level updates.
+
+### **BREAKING:** Updated supported Node.js versions to 22 and 24
+
+This release moves up the supported Node.js versions to 22 and 24, according to our [versioning policy](https://backstage.io/docs/overview/versioning-policy#nodejs-releases). As part of this change, we are also setting the compilation targets to ES2023, which lines up with the supported Node.js versions.
+
+The default TypeScript `target` compiler option is however left at ES2022. This is because older versions of TypeScript will error if they see an unsupported value, even if an override is set in your local `tsconfig.json`. You can still choose to set the TypeScript `target` to either `ES2023` or even `ES2024` in your own `tsconfig.json` if you prefer.
+
+### **BREAKING:** Switched modules to `ES2020` and resolution mode to `bundler`
+
+Now that the ecosystem has had some time to mature and adapt, we have finally switched over the default module resolution mode to `bundler` instead of `node`. The `module` setting was also changed to `ES2022`.
+
+You _may_ need to bump some dependencies as part of this change, or fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages, but if you depend on old versions or unmaintained packages, you may still notice some effects of this.
+
+You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings of your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
+
+A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
+
+### **BREAKING:** Moving toward updating `jest` and its DOM
+
+The `jest` test framework used to be a builtin dependency of the Backstage CLI. In this release, it has been made into a peer dependency to allow for easy upgrades to newer versions as you see fit.
+
+If you run tests using the Backstage CLI, you must therefore add Jest and its environment dependencies as `devDependencies` in your project root `package.json` when you upgrade.
+
+You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed.
+
+- **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29` in your root `package.json`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations.
+- **Jest 30**: Install `jest@^30`, `@types/jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27` in your root `package.json`. Fixes the stylesheet parsing warnings/errors, but requires migration steps.
+
+See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions.
+
+### **BREAKING:** Now using correct configuration options for Valkey
+
+If you are using Valkey as your backend cache provider, take note. It used to piggyback on the Redis configuration format, but that was not quite correct. So its config options have been updated to better match its capabilities.
+
+So if you use Valkey, you need to upgrade your `app-config.yaml`:
+
+```diff
+backend:
+ cache:
+ store: valkey
+ connection: ...
+ client:
+- namespace: 'my-app'
+- keyPrefixSeparator: ':'
++ keyPrefix: 'my-app:'
+- clearBatchSize: 1000
+- useUnlink: false
+```
+
+Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#31497](https://github.com/backstage/backstage/pull/31497)
+
+### **BREAKING:** Techdocs addon test utils removed explicit `screen`
+
+`TechdocsAddonTester.renderWithEffects()` used to return a `screen` that you could use for things like `getByText`. In order to support moving to newer versions of `@testing-library`, we have removed this, and you should import and use the normal `screen` from `@testing-library/react` instead.
+
+However: that `screen` can’t see inside the shadow DOM, which TechDocs uses. So if your tests need to look inside shadow DOM, you must install `shadow-dom-testing-library` and use its `screen` which provides special shadow-DOM queries, such as `getByShadowText` instead of `getByText.
+
+Example:
+
+```tsx
+import { screen } from 'shadow-dom-testing-library';
+// ... render the addon ...
+await TechDocsAddonTester.buildAddonsInTechDocs([])
+ .withDom(TEST_CONTENT)
+ .renderWithEffects();
+expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument();
+```
+
+### **BREAKING:** Backstage-UI advances!
+
+Again, Backstage-UI has a bunch of updates! One of them is breaking: If you were using the `Cell` component with text-specific props (`title` etc), you should now instead use the `CellText` component for that. Plain `Cell` is now meant to be used with generic child elements instead.
+
+### Simplified route compatibility
+
+If you are partway through a migration to the New Frontend System, you may enjoy the fact that the `useApp` and `useRouteRef` hooks now are forwards compatible with NFS. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs.
+
+### Support for Bitbucket Cloud OAuth
+
+Support for Bitbucket Cloud OAuth has been added - which was much welcome. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+As part of this change, some breaking changes were made to low level auth getter functions, such that they now return a promise instead of a plain value.
+
+Contributed by [@jksmth](https://github.com/jksmth) in [#31848](https://github.com/backstage/backstage/pull/31848)
+
+### New Catalog actions
+
+There are two new MCP-usable actions on the block for registering and unregistering entities: `catalog:register-entity` and `catalog:unregister-entity`.
+
+Contributed by [@gabemontero](https://github.com/gabemontero) in [#32042](https://github.com/backstage/backstage/pull/32042)
+
+### New `RepoOwnerPicker` for Scaffolder
+
+A new field extension for selecting an organization or owner for a particular repository has been shipped! Currently, this only supports GitHub autocomplete, but contributions welcome in making this available for other providers!
+
+Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#32105](https://github.com/backstage/backstage/pull/32105)
+
+## Security Fixes
+
+This release does not contain any security fixes.
+
+## Upgrade path
+
+We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated).
+
+## Links and References
+
+Below you can find a list of links and references to help you learn about and start using this new release.
+
+- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/)
+- [GitHub repository](https://github.com/backstage/backstage)
+- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy)
+- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support
+- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.46.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.47.0-next.0-changelog.md b/docs/releases/v1.47.0-next.0-changelog.md
new file mode 100644
index 0000000000..e1fd3f9ebb
--- /dev/null
+++ b/docs/releases/v1.47.0-next.0-changelog.md
@@ -0,0 +1,578 @@
+# Release v1.47.0-next.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.47.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.47.0-next.0)
+
+## @backstage/ui@0.11.0-next.0
+
+### Minor Changes
+
+- 4ea1d15: **BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency.
+
+### Patch Changes
+
+- 1880402: Fixes app background color on dark mode.
+- 9c76682: build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10
+- b4a4911: Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers).
+
+ Affected components: SearchField
+
+## @backstage/backend-defaults@0.14.1-next.0
+
+### Patch Changes
+
+- 7126bf2: Fixed a spelling mistake in root health service shutdown response.
+- Updated dependencies
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-dev-utils@0.1.6
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-node@0.2.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+
+## @backstage/backend-dynamic-feature-service@0.7.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/cli-node@0.2.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.40
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-events-backend@0.5.10-next.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/backend-openapi-utils@0.6.5-next.0
+
+### Patch Changes
+
+- 6678b78: Internal update to use native feature from our request validation library for handling base path determination.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## @backstage/backend-test-utils@1.10.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/cli@0.35.2-next.0
+
+### Patch Changes
+
+- 320c6a9: Bump `@swc/core` to support `ES2023` and `ES2024`
+- 9ee5996: Bump minimum required `@swc/core` to avoid transpilation bug
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-common@0.1.16
+ - @backstage/cli-node@0.2.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.0
+ - @backstage/integration@1.19.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
+## @backstage/create-app@0.7.8-next.0
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## @backstage/dev-utils@1.1.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-catalog-react@1.21.4
+
+## @techdocs/cli@1.10.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-common@0.1.16
+ - @backstage/config@1.3.6
+ - @backstage/plugin-techdocs-node@1.13.10
+
+## @backstage/plugin-app-visualizer@0.1.27-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-plugin-api@0.13.2
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.11-next.0
+
+### Patch Changes
+
+- e54fcb2: Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property)
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/plugin-auth-node@0.6.10
+
+## @backstage/plugin-catalog-backend@3.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-kubernetes-common@0.9.9
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-events-node@0.4.18
+
+## @backstage/plugin-devtools-backend@0.5.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-devtools-common@0.1.20
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+
+## @backstage/plugin-events-backend@0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.18
+
+## @backstage/plugin-mcp-actions-backend@0.1.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-client@1.12.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1
+
+## @backstage/plugin-mui-to-bui@0.2.3-next.0
+
+### Patch Changes
+
+- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0`
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/theme@0.7.1
+
+## @backstage/plugin-scaffolder-backend@3.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0
+
+## @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/backend-test-utils@1.10.3-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## @backstage/plugin-search-backend@2.0.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-search-common@1.2.21
+
+## @backstage/plugin-techdocs-backend@2.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-techdocs-node@1.13.10
+
+## @backstage/plugin-user-settings-backend@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## example-app@0.2.117-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/cli@0.35.2-next.0
+ - @backstage/plugin-mui-to-bui@0.2.3-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/integration-react@1.2.13
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-devtools@0.1.34
+ - @backstage/plugin-home@0.8.15
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-techdocs-react@1.3.6
+ - @backstage/plugin-user-settings@0.8.30
+
+## example-app-next@0.0.31-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/cli@0.35.2-next.0
+ - @backstage/plugin-app-visualizer@0.1.27-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/frontend-defaults@0.3.4
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/integration-react@1.2.13
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-app@0.3.3
+ - @backstage/plugin-auth@0.1.3
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-home@0.8.15
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-techdocs-react@1.3.6
+ - @backstage/plugin-user-settings@0.8.30
+
+## example-backend@0.0.46-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.9
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/plugin-auth-backend-module-github-provider@0.4.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7
+ - @backstage/plugin-devtools-backend@0.5.13-next.0
+ - @backstage/plugin-events-backend@0.5.10-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7
+ - @backstage/plugin-kubernetes-backend@0.21.0
+ - @backstage/plugin-mcp-actions-backend@0.1.7-next.0
+ - @backstage/plugin-notifications-backend@0.6.1
+ - @backstage/plugin-permission-backend@0.7.7
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-proxy-backend@0.6.9
+ - @backstage/plugin-scaffolder-backend@3.1.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17
+ - @backstage/plugin-search-backend@2.0.10-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.11
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9
+ - @backstage/plugin-search-backend-module-explore@0.3.10
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-signals-backend@0.3.11
+ - @backstage/plugin-techdocs-backend@2.1.4-next.0
+
+## e2e-test@0.2.36-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.7.8-next.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/errors@1.2.7
+
+## techdocs-cli-embedded-app@0.2.116-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/cli@0.35.2-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/test-utils@1.7.14
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-techdocs-react@1.3.6
diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md
index c8d9034955..6f1b7fd54a 100644
--- a/docs/tooling/cli/02-build-system.md
+++ b/docs/tooling/cli/02-build-system.md
@@ -610,7 +610,7 @@ With that in mind, here are some IDEs configurations to run backstage components
1. Click on "Edit Configurations" on top panel
2. In the modal dialog click on link "Edit configuration templates..." located in the bottom left corner.
3. "Configuration file": leave empty (`backstage-cli` adds the config)
- 4. "Node options": `--no-node-snapshot --experimental-vm-modules`
+ 4. "Node options": ` --experimental-vm-modules`
5. "Jest package": `~/workspace/backstage/node_modules/@backstage/cli` - the location of the backstage cli package.
6. "Working directory": `~/workspace/backstage`
7. "Jest Options": `repo test --runInBand --watch=false`
@@ -621,20 +621,21 @@ With that in mind, here are some IDEs configurations to run backstage components
#### VS Code
+1. Install the [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) for VS Code.
+2. Update `settings.json` in the `.vscode` folder with:
+
```jsonc
{
"jest.jestCommandLine": "yarn test",
// In a large repo like the Backstage main repo you likely want to disable
// watch mode and the initial test run too, leaving just manual and perhaps
// on-save test runs in place.
- "jest.autoRun": {
- "watch": false,
- "onSave": "test-src-file"
- }
+ "jest.runMode": "on-save"
}
```
-A complete launch configuration for VS Code debugging may look like this:
+3. Add a launch configuration for VS Code in `launch.json` in the `.vscode` folder.
+ A complete configuration for debugging may look like this:
```jsonc
{
@@ -653,11 +654,13 @@ A complete launch configuration for VS Code debugging may look like this:
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
- "disableOptimisticBPs": true,
- "program": "${workspaceFolder}/node_modules/.bin/backstage-cli"
+ "program": "${workspaceFolder}/node_modules/@backstage/cli/bin/backstage-cli"
}
```
+4. The configuration is not for manual runs from the "Run and Debug" view.
+ Instead use the Jest test explorer or the [test's gutter menu](https://github.com/jest-community/vscode-jest#how-to-trigger-a-test-run).
+
## Publishing
Package publishing is an optional part of the Backstage build system and not
diff --git a/docs/tutorials/jest30-migration.md b/docs/tutorials/jest30-migration.md
new file mode 100644
index 0000000000..ea79b03009
--- /dev/null
+++ b/docs/tutorials/jest30-migration.md
@@ -0,0 +1,95 @@
+---
+id: jest30-migration
+title: Migrating to Jest 30
+description: A guide to migrating your project to Jest 30 and JSDOM 27
+---
+
+Starting with a recent version of `@backstage/cli`, `jest` is a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project.
+
+You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed.
+
+- **Jest 29**:
+
+ ```bash
+ # in your repository root, run:
+ yarn add --dev jest@^29 @types/jest@^29 jest-environment-jsdom@^29
+ ```
+
+ Pros: No migration needed
+
+ Cons: You may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations
+
+- **Jest 30**:
+
+ ```bash
+ # in your repository root, run:
+ yarn add --dev jest@^30 @types/jest@^30 @jest/environment-jsdom-abstract@^30 jsdom@^27
+ ```
+
+ Pros: Fixes the stylesheet parsing warnings/errors
+
+ Cons: Requires migration steps (see below) in your tests
+
+## Migration Guide
+
+The examples below are issues we encountered while migrating the Backstage repository. For a complete list of breaking changes, see the official documentation:
+
+- [Jest 30 upgrade guide](https://jestjs.io/docs/upgrading-to-jest30)
+- [JSDOM changelog](https://github.com/jsdom/jsdom/releases)
+
+### Jest 30
+
+**Asymmetric matchers with arrays**: `expect.objectContaining()` no longer works with arrays.
+
+```diff
+- expect(result).toEqual(expect.objectContaining([{ id: '123' }]));
++ expect(result).toEqual([{ id: '123' }]);
+// or
++ expect(result).toEqual(expect.arrayContaining([{ id: '123' }]));
+```
+
+**Array length assertions**: `expect.objectContaining({ length: N })` no longer works.
+
+```diff
+- expect(fn).toHaveBeenCalledWith(expect.objectContaining({ length: 2 }));
++ expect(fn).toHaveBeenCalledWith(expect.any(Array));
++ expect(fn.mock.calls[0][0]).toHaveLength(2);
+```
+
+**Deprecated matcher aliases removed**: Replace with canonical names.
+
+```diff
+- expect(fn).toBeCalled();
++ expect(fn).toHaveBeenCalled();
+```
+
+**Snapshots**: Regenerate snapshots as the header format has changed.
+
+```bash
+yarn test --no-watch -u
+```
+
+### JSDOM 27
+
+**window.location is non-configurable**: You can no longer mock location via `Object.defineProperty`.
+
+```diff
+- Object.defineProperty(window, 'location', { value: { href: '' } });
++ // Option 1: Use history API
++ history.replaceState({}, '', '/new-path');
++ // Option 2: Spy on navigation methods
++ const spy = jest.spyOn(component, 'navigate');
+```
+
+**CSS color values**: Colors may be returned as RGB instead of named colors.
+
+```diff
+- expect(element.style.color).toBe('red');
++ expect(element.style.color).toBe('rgb(255, 0, 0)');
+```
+
+**Error format changes**: Error messages and stack traces may have different formatting.
+
+#### If you run into `Cannot read properties of null (reading 'constructor')`
+
+Certain Backstage UI-components (e.g. Button) have a combination of CSS that triggers this error in tests. The solution is to make sure you have at least v0.9.25 of `@acemir/cssom`.
diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md
index 237a7207a1..bb416bf731 100644
--- a/docs/tutorials/using-backstage-proxy-within-plugin.md
+++ b/docs/tutorials/using-backstage-proxy-within-plugin.md
@@ -94,15 +94,15 @@ export const AwesomeUsersTable = () => {
This section describes the steps to wrap your API client in a [Utility API](../api/utility-apis.md), which are:
-- use [`createApiRef`](../reference/frontend-plugin-api.createapiref.md) to create a
- new [`ApiRef`](../reference/frontend-plugin-api.apiref.md)
-- register an [`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) with
+- use [`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) to create a
+ new [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)
+- register an [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) with
your plugin using
- [`createApiFactory`](../reference/frontend-plugin-api.createapifactory.md). This
+ [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html). This
will wrap your API implementation, associate your `ApiRef` with your
implementation and tell backstage how to instantiate it
- finally, you can use your API in your components by calling
- [`useApi`](../reference/frontend-plugin-api.useapi.md)
+ [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html)
### Defining the API client interface
@@ -187,8 +187,8 @@ export class MyAwesomeApiClient implements MyAwesomeApi {
```
> Check out the docs for more information on the
-> [DiscoveryApi](../reference/frontend-plugin-api.discoveryapi.md) or the
-> [FetchApi](../reference/frontend-plugin-api.fetchapi.md)
+> [DiscoveryApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.DiscoveryApi.html) or the
+> [FetchApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.FetchApi.html)
### Bundling your ApiRef with your plugin
@@ -233,7 +233,7 @@ export const myCustomPlugin = createPlugin({
### Using the API in your components
Now you should be able to access your API using the backstage hook
-[`useApi`](../reference/frontend-plugin-api.useapi.md) from within your plugin code.
+[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) from within your plugin code.
```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx"
import { useApi } from '@backstage/core-plugin-api';
diff --git a/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx
new file mode 100644
index 0000000000..0f8bdd21cf
--- /dev/null
+++ b/microsite/blog/2025-12-30-backstage-wrapped-2025.mdx
@@ -0,0 +1,115 @@
+---
+title: 'Backstage Wrapped 2025: Celebrating a mature framework with modern foundations'
+author: Vincenzo Scamporlino, Spotify
+authorURL: https://github.com/vinzscam
+authorImageURL: https://avatars.githubusercontent.com/u/8433119?v=8
+---
+
+
+
+This year was another big one for Backstage and our worldwide community of adopters, contributors, and partners. Backstage turned five, the New Frontend System became adoption-ready, and your favorite platform for developer experience gained AI superpowers with the introduction of the Actions Registry and MCP server support. Let's blast into the New Year with a quick look back, along with a peek around the corner at what's coming next.
+
+{/* truncate */}
+
+---
+
+## Busy beavers here, there, and everywhere
+
+As in years past, we've visualized how our vibrant global community lights up the Backstage open source repo with enthusiasm, creativity, and commits. This year, we're visualizing contributions to the Community Plugins repo, too. Can you spot your name in the videos below?
+
+
+
+_All 7.7k commits to `backstage/backstage` in 2025_
+
+
+
+_All 3.3k commits to `backstage/community-plugins` in 2025_
+
+Couldn't spot your name in the videos? Here's one more chance — every contributor who helped shape Backstage in 2025, all in one picture.
+
+
+
+## Mature for a five-year-old
+
+
+_The growing Backstage ecosystem, by the numbers_
+
+Earlier this year, the Backstage project [celebrated its 5th birthday][bday] on the main stage at [KubeCon + CloudNativeCon in London][kcon-eu].
+
+While the framework still isn't old enough to shave, it's matured into the industry standard for internal developer portals (IDPs). According to [this DX survey][dx] released in March:
+
+> Backstage is dominating the IDP market, holding an impressive 89% market share compared to SaaS competitors and achieving a remarkable 67% overall market penetration. These percentages are even higher for enterprise companies.
+
+As you can see above in the [latest project stats][video], the Backstage ecosystem continues to expand, growing in adopters and contributors, as well as plugins and third-party service providers.
+
+Want to dive into more Backstage stats? Check out the project's health scores and more on the relaunched [LFX Insights][lfx] site.
+
+[bday]: https://www.youtube.com/watch?v=JqG1wey7-Ao&t=563s&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+[kcon-eu]: https://backstage.io/blog/2025/04/29/backstagecon-kubecon-25-london#big-birthday-energy-on-the-big-stage
+[dx]: https://newsletter.getdx.com/p/backstage-and-the-developer-portal-market
+[video]: https://www.youtube.com/watch?v=Wih0a1v1Et0&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU
+[lfx]: https://insights.linuxfoundation.org/project/backstage
+
+## Building the future on modern foundations
+
+Just because Backstage has cemented its place as the standard for IDPs doesn't mean it's sitting still. From backend to frontend, from AI to UI, the technical foundations of the platform have been modernized, updated, and revamped — this is why today's Backstage is ready to build the future of developer experience.
+
+Top 2025 highlights:
+
+- Goodbye to the old backend system
+- New Frontend System is adoption-ready
+- Hello to the Actions Registry + MCP server support
+- Introducing a new design system with Backstage UI
+
+With the [New Backend System][backend] released as a stable 1.0 last year and the [New Frontend System][frontend] becoming [adoption-ready][1-42] this year, Backstage is easier than ever to use, maintain, and build on — making it both more accessible and more capable for adopters, old and new.
+
+This year also saw the debut of the [Actions Registry][actions] and MCP server support — so now Backstage can empower both your human developers and their AI helpers.
+
+And [Backstage UI][ui] is coming! The alpha release of the upcoming design system made huge strides in 2025, preparing the way for a new level of usability throughout the Backstage experience.
+
+Catch up on a whole year's worth of improvements — and see where Backstage is heading next — in the recordings below.
+
+[backend]: https://backstage.io/docs/backend-system/
+[frontend]: https://backstage.io/docs/frontend-system/
+[1-42]: https://backstage.io/docs/releases/v1.42.0
+[actions]: https://backstage.io/docs/backend-system/core-services/actions-registry
+[ui]: https://ui.backstage.io/
+
+
+
+_Watch the Backstage maintainers' talk at [KubeCon in London][lon], where they demoed the New Frontend System and introduced the Actions Registry._
+
+
+_Watch the maintainers' talk at [KubeCon in Atlanta][atl], where they shared
+work towards an AI-Native Backstage, progress on Backstage UI, and more._
+
+[lon]: https://backstage.io/blog/2025/04/29/backstagecon-kubecon-25-london
+[atl]: https://backstage.io/blog/2025/11/25/backstagecon-kubecon-25-atlanta
+
+## See you in 2026!
+
+Happy New Year, Backstage Community! See you in the repos!
diff --git a/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png b/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png
new file mode 100644
index 0000000000..7aa16f106e
Binary files /dev/null and b/microsite/blog/assets/2025-12-30/contributors-wrapped-2025.png differ
diff --git a/microsite/blog/assets/2025-12-30/wrapped2025-stats.png b/microsite/blog/assets/2025-12-30/wrapped2025-stats.png
new file mode 100644
index 0000000000..59d31f142c
Binary files /dev/null and b/microsite/blog/assets/2025-12-30/wrapped2025-stats.png differ
diff --git a/microsite/blog/assets/2025-12-30/wrapped2025.png b/microsite/blog/assets/2025-12-30/wrapped2025.png
new file mode 100644
index 0000000000..b99768dfda
Binary files /dev/null and b/microsite/blog/assets/2025-12-30/wrapped2025.png differ
diff --git a/microsite/data/plugins/sds_workspace.yaml b/microsite/data/plugins/sds_workspace.yaml
new file mode 100644
index 0000000000..43d9a0f20e
--- /dev/null
+++ b/microsite/data/plugins/sds_workspace.yaml
@@ -0,0 +1,10 @@
+---
+title: Citrix Secure Developer Spaces (SDS)
+author: Citrix
+authorUrl: https://github.com/strong-network
+category: Development
+description: Connect Backstage with Citrix Secure Developer Spaces (SDS) to view and create developer workspaces.
+documentation: https://www.npmjs.com/package/@citrixcloud/backstage-sds-workspaces
+iconUrl: /img/citrix_sds.png
+npmPackageName: '@citrixcloud/backstage-sds-workspaces'
+addedDate: '2025-10-07'
diff --git a/microsite/package.json b/microsite/package.json
index a3430545f4..edf07abb02 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -28,7 +28,7 @@
"@docusaurus/preset-classic": "^3.1.1",
"@docusaurus/types": "^3.1.1",
"@mdx-js/react": "^3.0.0",
- "@swc/core": "^1.3.46",
+ "@swc/core": "^1.15.6",
"clsx": "^2.0.0",
"docusaurus-plugin-openapi-docs": "^4.3.0",
"docusaurus-plugin-sass": "^0.2.3",
diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts
index 9a4f03620f..afe5616bbf 100644
--- a/microsite/sidebars.ts
+++ b/microsite/sidebars.ts
@@ -18,664 +18,743 @@ const scaffolderSidebar = tryToLoadCustomSidebar(
'../docs/features/software-templates/api/sidebar.ts',
);
+function sidebarElementWithIndex(
+ element: {
+ description?: string;
+ differentiator?: string;
+ label: string;
+ docId?: string;
+ },
+ children: Array,
+) {
+ const { label, description, differentiator } = element;
+ return {
+ type: 'category',
+ label: label,
+ description,
+ link: {
+ type: 'generated-index',
+ title: label,
+ slug: `/${differentiator}${label
+ .toLowerCase()
+ .replace(/[^a-z0-9 _-]/gi, '-')}/generated-index`,
+ },
+ items: children,
+ };
+}
+
export default {
- docs: {
- Overview: [
+ docs: [
+ sidebarElementWithIndex({ label: 'Overview' }, [
'overview/what-is-backstage',
'overview/technical-overview',
'overview/architecture-overview',
'overview/roadmap',
'overview/threat-model',
'overview/versioning-policy',
- ],
- 'Getting Started': [
+ ]),
+ sidebarElementWithIndex({ label: 'Getting Started' }, [
'getting-started/index',
- {
- type: 'category',
- label: 'Configuring Backstage',
- items: [
- 'getting-started/config/database',
- 'getting-started/config/authentication',
- 'getting-started/configure-app-with-plugins',
- 'getting-started/homepage',
- ],
- },
- {
- type: 'category',
- label: 'Deploying Backstage',
- items: [
- 'deployment/index',
- 'deployment/scaling',
- 'deployment/docker',
- 'deployment/k8s',
- ],
- },
- {
- type: 'category',
- label: 'Using Backstage',
- items: [
- 'getting-started/logging-in',
- 'getting-started/register-a-component',
- 'getting-started/create-a-component',
- ],
- },
+ sidebarElementWithIndex({ label: 'Configuring Backstage' }, [
+ 'getting-started/config/database',
+ 'getting-started/config/authentication',
+ 'getting-started/configure-app-with-plugins',
+ 'getting-started/homepage',
+ ]),
+ sidebarElementWithIndex({ label: 'Deploying Backstage' }, [
+ 'deployment/index',
+ 'deployment/scaling',
+ 'deployment/docker',
+ 'deployment/k8s',
+ ]),
+ sidebarElementWithIndex({ label: 'Using Backstage' }, [
+ 'getting-started/logging-in',
+ 'getting-started/register-a-component',
+ 'getting-started/create-a-component',
+ ]),
'overview/support',
'getting-started/keeping-backstage-updated',
- ],
+ ]),
...(process.env.GOLDEN_PATH
- ? {
- 'Golden Paths': [
+ ? [
+ sidebarElementWithIndex({ label: 'Golden Paths' }, [
+ sidebarElementWithIndex({ label: '001 - Create an App' }, [
+ 'golden-path/create-app/index',
+ 'golden-path/create-app/npx-create-app',
+ 'golden-path/create-app/local-development',
+ 'golden-path/create-app/installing-plugins',
+ 'golden-path/create-app/logging-in',
+ 'golden-path/create-app/custom-theme',
+ 'golden-path/create-app/keeping-backstage-updated',
+ ]),
+ sidebarElementWithIndex({ label: '002 - Plugins' }, [
+ 'golden-path/plugins/index',
+ 'golden-path/plugins/why-build-plugins',
+ 'golden-path/plugins/sustainable-plugin-development',
+ sidebarElementWithIndex({ label: 'Backend Plugins' }, [
+ 'golden-path/plugins/backend/001-first-steps',
+ 'golden-path/plugins/backend/002-poking-around',
+ ]),
+ ]),
+ ]),
+ ]
+ : []),
+ sidebarElementWithIndex(
+ {
+ label: 'Core Features',
+ description: 'Features powering the core of Backstage.',
+ },
+ [
+ sidebarElementWithIndex(
+ {
+ label: 'Auth and Identity',
+ description: 'Authentication and identity management features.',
+ },
+ [
+ 'auth/index',
+ sidebarElementWithIndex(
+ {
+ label: 'Included providers',
+ description:
+ 'Pre-configured authentication providers included with Backstage.',
+ },
+ [
+ 'auth/auth0/provider',
+ 'auth/atlassian/provider',
+ 'auth/aws-alb/provider',
+ 'auth/microsoft/provider',
+ 'auth/microsoft/easy-auth',
+ 'auth/bitbucket/provider',
+ 'auth/bitbucketServer/provider',
+ 'auth/cloudflare/provider',
+ 'auth/github/provider',
+ 'auth/gitlab/provider',
+ 'auth/google/provider',
+ 'auth/google/gcp-iap-auth',
+ 'auth/guest/provider',
+ 'auth/okta/provider',
+ 'auth/oauth2-proxy/provider',
+ 'auth/onelogin/provider',
+ 'auth/vmware-cloud/provider',
+ ],
+ ),
+ 'auth/identity-resolver',
+ 'auth/oauth',
+ 'auth/oidc',
+ 'auth/add-auth-provider',
+ 'auth/service-to-service-auth',
+ 'auth/autologout',
+ 'auth/troubleshooting',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Kubernetes',
+ description: 'Use Kubernetes right from Backstage.',
+ },
+ [
+ 'features/kubernetes/overview',
+ 'features/kubernetes/installation',
+ 'features/kubernetes/configuration',
+ 'features/kubernetes/authentication',
+ 'features/kubernetes/authentication-strategies',
+ 'features/kubernetes/troubleshooting',
+ 'features/kubernetes/proxy',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Notifications',
+ description: 'Know when important events happen in Backstage.',
+ },
+ [
+ 'notifications/index',
+ 'notifications/processors',
+ 'notifications/usage',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Permissions',
+ description: 'Access control for your Backstage instance.',
+ },
+ [
+ 'permissions/overview',
+ 'permissions/concepts',
+ 'permissions/getting-started',
+ 'permissions/writing-a-policy',
+ 'permissions/frontend-integration',
+ 'permissions/custom-rules',
+ sidebarElementWithIndex(
+ {
+ label: 'Tutorial: using Permissions in your plugin',
+ description:
+ 'Step-by-step guide for plugin authors on using Permissions.',
+ },
+ [
+ 'permissions/plugin-authors/01-setup',
+ 'permissions/plugin-authors/02-adding-a-basic-permission-check',
+ 'permissions/plugin-authors/03-adding-a-resource-permission-check',
+ 'permissions/plugin-authors/04-authorizing-access-to-paginated-data',
+ 'permissions/plugin-authors/05-frontend-authorization',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
+ { label: 'Search', description: 'Using Search within Backstage.' },
+ [
+ 'features/search/search-overview',
+ 'features/search/getting-started',
+ 'features/search/concepts',
{
type: 'category',
- label: '001 - create-app',
- items: [
- 'golden-path/create-app/index',
- 'golden-path/create-app/npx-create-app',
- 'golden-path/create-app/local-development',
- 'golden-path/create-app/installing-plugins',
- 'golden-path/create-app/logging-in',
- 'golden-path/create-app/custom-theme',
- 'golden-path/create-app/keeping-backstage-updated',
- ],
+ label: 'API',
+ link:
+ searchSidebar.length > 0
+ ? {
+ type: 'generated-index',
+ title: 'Search API',
+ slug: '/category/search-api',
+ }
+ : {
+ type: 'doc',
+ id: 'openapi/generated-docs/404',
+ },
+ items: searchSidebar,
},
+ 'features/search/architecture',
+ 'features/search/search-engines',
+ 'features/search/collators',
+ 'features/search/how-to-guides',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Software Catalog',
+ description: 'Manage and explore your software components.',
+ },
+ [
+ 'features/software-catalog/software-catalog-overview',
+ 'features/software-catalog/life-of-an-entity',
+ 'features/software-catalog/configuration',
+ 'features/software-catalog/system-model',
+ 'features/software-catalog/descriptor-format',
+ 'features/software-catalog/references',
+ 'features/software-catalog/well-known-annotations',
+ 'features/software-catalog/well-known-relations',
+ 'features/software-catalog/well-known-statuses',
+ 'features/software-catalog/extending-the-model',
+ 'features/software-catalog/external-integrations',
+ 'features/software-catalog/catalog-customization',
{
type: 'category',
- label: '002 - Plugins',
- items: [
- 'golden-path/plugins/index',
- 'golden-path/plugins/why-build-plugins',
- 'golden-path/plugins/sustainable-plugin-development',
- {
- type: 'category',
- label: 'Backend Plugins',
- items: [
- 'golden-path/plugins/backend/001-first-steps',
- 'golden-path/plugins/backend/002-poking-around',
- ],
- },
- ],
+ label: 'API',
+ link:
+ catalogSidebar.length > 0
+ ? {
+ type: 'generated-index',
+ title: 'Catalog API',
+ slug: '/category/catalog-api',
+ }
+ : {
+ type: 'doc',
+ id: 'openapi/generated-docs/404',
+ },
+ items: catalogSidebar,
+ },
+ 'features/software-catalog/creating-the-catalog-graph',
+ 'features/software-catalog/faq',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Software Templates',
+ description: 'Create and manage software templates.',
+ },
+ [
+ 'features/software-templates/software-templates-index',
+ 'features/software-templates/configuration',
+ 'features/software-templates/adding-templates',
+ 'features/software-templates/writing-templates',
+ 'features/software-templates/input-examples',
+ 'features/software-templates/builtin-actions',
+ 'features/software-templates/writing-custom-actions',
+ 'features/software-templates/writing-tests-for-actions',
+ 'features/software-templates/writing-custom-field-extensions',
+ 'features/software-templates/writing-custom-step-layouts',
+ 'features/software-templates/authorizing-scaffolder-template-details',
+ 'features/software-templates/migrating-to-rjsf-v5',
+ 'features/software-templates/migrating-from-v1beta2-to-v1beta3',
+ 'features/software-templates/dry-run-testing',
+ 'features/software-templates/experimental',
+ 'features/software-templates/templating-extensions',
+ {
+ type: 'category',
+ label: 'API',
+ link:
+ scaffolderSidebar.length > 0
+ ? {
+ type: 'generated-index',
+ title: 'Scaffolder API',
+ slug: '/category/scaffolder-api',
+ }
+ : {
+ type: 'doc',
+ id: 'openapi/generated-docs/404',
+ },
+ items: scaffolderSidebar,
},
],
- }
- : {}),
- 'Core Features': [
- {
- type: 'category',
- label: 'Auth and Identity',
- items: [
- 'auth/index',
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Included providers',
- items: [
- 'auth/auth0/provider',
- 'auth/atlassian/provider',
- 'auth/aws-alb/provider',
- 'auth/microsoft/provider',
- 'auth/microsoft/easy-auth',
- 'auth/bitbucket/provider',
- 'auth/bitbucketServer/provider',
- 'auth/cloudflare/provider',
- 'auth/github/provider',
- 'auth/gitlab/provider',
- 'auth/google/provider',
- 'auth/google/gcp-iap-auth',
- 'auth/guest/provider',
- 'auth/okta/provider',
- 'auth/oauth2-proxy/provider',
- 'auth/onelogin/provider',
- 'auth/vmware-cloud/provider',
- ],
+ label: 'TechDocs',
+ description: 'Documentation as code for your software.',
},
- 'auth/identity-resolver',
- 'auth/oauth',
- 'auth/oidc',
- 'auth/add-auth-provider',
- 'auth/service-to-service-auth',
- 'auth/autologout',
- 'auth/troubleshooting',
- ],
- },
+ [
+ 'features/techdocs/techdocs-overview',
+ 'features/techdocs/getting-started',
+ 'features/techdocs/concepts',
+ 'features/techdocs/addons',
+ 'features/techdocs/architecture',
+ 'features/techdocs/extensions',
+ 'features/techdocs/creating-and-publishing',
+ 'features/techdocs/configuration',
+ 'features/techdocs/using-cloud-storage',
+ 'features/techdocs/configuring-ci-cd',
+ 'features/techdocs/cli',
+ 'features/techdocs/how-to-guides',
+ 'features/techdocs/troubleshooting',
+ 'features/techdocs/faqs',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Kubernetes',
- items: [
- 'features/kubernetes/overview',
- 'features/kubernetes/installation',
- 'features/kubernetes/configuration',
- 'features/kubernetes/authentication',
- 'features/kubernetes/authentication-strategies',
- 'features/kubernetes/troubleshooting',
- 'features/kubernetes/proxy',
- ],
+ label: 'Integrations',
+ description: 'Connect and integrate with external services.',
},
- {
- type: 'category',
- label: 'Notifications',
- items: [
- 'notifications/index',
- 'notifications/processors',
- 'notifications/usage',
- ],
- },
- {
- type: 'category',
- label: 'Permissions',
- items: [
- 'permissions/overview',
- 'permissions/concepts',
- 'permissions/getting-started',
- 'permissions/writing-a-policy',
- 'permissions/frontend-integration',
- 'permissions/custom-rules',
- {
- type: 'category',
- label: 'Tutorial: using Permissions in your plugin',
- items: [
- 'permissions/plugin-authors/01-setup',
- 'permissions/plugin-authors/02-adding-a-basic-permission-check',
- 'permissions/plugin-authors/03-adding-a-resource-permission-check',
- 'permissions/plugin-authors/04-authorizing-access-to-paginated-data',
- 'permissions/plugin-authors/05-frontend-authorization',
- ],
- },
- ],
- },
- {
- type: 'category',
- label: 'Search',
- items: [
- 'features/search/search-overview',
- 'features/search/getting-started',
- 'features/search/concepts',
- {
- type: 'category',
- label: 'API',
- link:
- searchSidebar.length > 0
- ? {
- type: 'generated-index',
- title: 'Search API',
- slug: '/category/search-api',
- }
- : {
- type: 'doc',
- id: 'openapi/generated-docs/404',
- },
- items: searchSidebar,
- },
- 'features/search/architecture',
- 'features/search/search-engines',
- 'features/search/collators',
- 'features/search/how-to-guides',
- ],
- },
- {
- type: 'category',
- label: 'Software Catalog',
- items: [
- 'features/software-catalog/software-catalog-overview',
- 'features/software-catalog/life-of-an-entity',
- 'features/software-catalog/configuration',
- 'features/software-catalog/system-model',
- 'features/software-catalog/descriptor-format',
- 'features/software-catalog/references',
- 'features/software-catalog/well-known-annotations',
- 'features/software-catalog/well-known-relations',
- 'features/software-catalog/well-known-statuses',
- 'features/software-catalog/extending-the-model',
- 'features/software-catalog/external-integrations',
- 'features/software-catalog/catalog-customization',
- {
- type: 'category',
- label: 'API',
- link:
- catalogSidebar.length > 0
- ? {
- type: 'generated-index',
- title: 'Catalog API',
- slug: '/category/catalog-api',
- }
- : {
- type: 'doc',
- id: 'openapi/generated-docs/404',
- },
- items: catalogSidebar,
- },
- 'features/software-catalog/creating-the-catalog-graph',
- 'features/software-catalog/faq',
- ],
- },
- {
- type: 'category',
- label: 'Software Templates',
- items: [
- 'features/software-templates/software-templates-index',
- 'features/software-templates/configuration',
- 'features/software-templates/adding-templates',
- 'features/software-templates/writing-templates',
- 'features/software-templates/input-examples',
- 'features/software-templates/builtin-actions',
- 'features/software-templates/writing-custom-actions',
- 'features/software-templates/writing-tests-for-actions',
- 'features/software-templates/writing-custom-field-extensions',
- 'features/software-templates/writing-custom-step-layouts',
- 'features/software-templates/authorizing-scaffolder-template-details',
- 'features/software-templates/migrating-to-rjsf-v5',
- 'features/software-templates/migrating-from-v1beta2-to-v1beta3',
- 'features/software-templates/dry-run-testing',
- 'features/software-templates/experimental',
- 'features/software-templates/templating-extensions',
- {
- type: 'category',
- label: 'API',
- link:
- scaffolderSidebar.length > 0
- ? {
- type: 'generated-index',
- title: 'Scaffolder API',
- slug: '/category/scaffolder-api',
- }
- : {
- type: 'doc',
- id: 'openapi/generated-docs/404',
- },
- items: scaffolderSidebar,
- },
- ],
- },
- {
- type: 'category',
- label: 'TechDocs',
- items: [
- 'features/techdocs/techdocs-overview',
- 'features/techdocs/getting-started',
- 'features/techdocs/concepts',
- 'features/techdocs/addons',
- 'features/techdocs/architecture',
- 'features/techdocs/extensions',
- 'features/techdocs/creating-and-publishing',
- 'features/techdocs/configuration',
- 'features/techdocs/using-cloud-storage',
- 'features/techdocs/configuring-ci-cd',
- 'features/techdocs/cli',
- 'features/techdocs/how-to-guides',
- 'features/techdocs/troubleshooting',
- 'features/techdocs/faqs',
- ],
- },
- ],
- Integrations: [
- 'integrations/index',
- {
- type: 'category',
- label: 'AWS S3',
- items: [
+ [
+ 'integrations/index',
+ sidebarElementWithIndex({ label: 'AWS S3' }, [
'integrations/aws-s3/locations',
'integrations/aws-s3/discovery',
- ],
- },
- {
- type: 'category',
- label: 'Azure Blob Storage',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Azure Blob Storage' }, [
'integrations/azure-blobStorage/locations',
'integrations/azure-blobStorage/discovery',
- ],
- },
- {
- type: 'category',
- label: 'Azure',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Azure' }, [
'integrations/azure/locations',
'integrations/azure/discovery',
'integrations/azure/org',
- ],
- },
- {
- type: 'category',
- label: 'Bitbucket Cloud',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Bitbucket Cloud' }, [
'integrations/bitbucketCloud/locations',
'integrations/bitbucketCloud/discovery',
- ],
- },
- {
- type: 'category',
- label: 'Bitbucket Server',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Bitbucket Server' }, [
'integrations/bitbucketServer/locations',
'integrations/bitbucketServer/discovery',
- ],
- },
- {
- type: 'category',
- label: 'Datadog',
- items: ['integrations/datadog-rum/installation'],
- },
- {
- type: 'category',
- label: 'Gerrit',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Datadog' }, [
+ 'integrations/datadog-rum/installation',
+ ]),
+ sidebarElementWithIndex({ label: 'Gerrit' }, [
'integrations/gerrit/locations',
'integrations/gerrit/discovery',
- ],
- },
- {
- type: 'category',
- label: 'GitHub',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'Github' }, [
'integrations/github/locations',
'integrations/github/discovery',
'integrations/github/org',
'integrations/github/github-apps',
- ],
- },
- {
- type: 'category',
- label: 'GitLab',
- items: [
+ ]),
+ sidebarElementWithIndex({ label: 'GitLab' }, [
'integrations/gitlab/locations',
'integrations/gitlab/discovery',
'integrations/gitlab/org',
- ],
- },
+ ]),
+ sidebarElementWithIndex({ label: 'Gitea' }, [
+ 'integrations/gitea/locations',
+ 'integrations/gitea/discovery',
+ ]),
+ sidebarElementWithIndex({ label: 'Harness' }, [
+ 'integrations/harness/locations',
+ ]),
+ sidebarElementWithIndex({ label: 'Google GCS' }, [
+ 'integrations/google-cloud-storage/locations',
+ ]),
+ sidebarElementWithIndex({ label: 'LDAP' }, ['integrations/ldap/org']),
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Gitea',
- items: ['integrations/gitea/locations', 'integrations/gitea/discovery'],
+ label: 'Plugins',
+ description: 'Extend Backstage with custom functionality.',
},
- {
- type: 'category',
- label: 'Harness',
- items: ['integrations/harness/locations'],
- },
- {
- type: 'category',
- label: 'Google GCS',
- items: ['integrations/google-cloud-storage/locations'],
- },
- {
- type: 'category',
- label: 'LDAP',
- items: ['integrations/ldap/org'],
- },
- ],
- Plugins: [
- 'plugins/index',
- 'plugins/existing-plugins',
- 'plugins/create-a-plugin',
- 'plugins/plugin-development',
- 'plugins/structure-of-a-plugin',
- 'plugins/integrating-plugin-into-software-catalog',
- 'plugins/integrating-search-into-plugins',
- 'plugins/composability',
- 'plugins/internationalization',
- 'plugins/analytics',
- 'plugins/feature-flags',
- {
- type: 'category',
- label: 'OpenAPI',
- items: [
- 'openapi/01-getting-started',
- 'openapi/generate-client',
- 'openapi/test-case-validation',
- ],
- },
- {
- type: 'category',
- label: 'Backends and APIs',
- items: [
- 'plugins/proxying',
- 'plugins/backend-plugin',
- 'plugins/call-existing-api',
- ],
- },
- {
- type: 'category',
- label: 'Testing',
- items: ['plugins/testing'],
- },
- {
- type: 'category',
- label: 'Publishing',
- items: [
- 'plugins/publish-private',
- 'plugins/add-to-directory',
- 'plugins/observability',
- ],
- },
- ],
- Configuration: [
- 'conf/index',
- 'conf/reading',
- 'conf/writing',
- 'conf/defining',
- ],
- Framework: [
- {
- type: 'category',
- label: 'Backend System',
- items: [
- 'backend-system/index',
+ [
+ 'plugins/index',
+ 'plugins/existing-plugins',
+ 'plugins/create-a-plugin',
+ 'plugins/plugin-development',
+ 'plugins/structure-of-a-plugin',
+ 'plugins/integrating-plugin-into-software-catalog',
+ 'plugins/integrating-search-into-plugins',
+ 'plugins/composability',
+ 'plugins/internationalization',
+ 'plugins/analytics',
+ 'plugins/feature-flags',
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Architecture',
- items: [
- 'backend-system/architecture/index',
- 'backend-system/architecture/services',
- 'backend-system/architecture/plugins',
- 'backend-system/architecture/extension-points',
- 'backend-system/architecture/modules',
- 'backend-system/architecture/feature-loaders',
- 'backend-system/architecture/naming-patterns',
- ],
+ label: 'OpenAPI',
+ description:
+ 'Work with OpenAPI specifications and generate clients.',
},
+ [
+ 'openapi/01-getting-started',
+ 'openapi/generate-client',
+ 'openapi/test-case-validation',
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Building Backends',
- items: [
- 'backend-system/building-backends/index',
- 'backend-system/building-backends/migrating',
- ],
+ label: 'Backends and APIs',
+ description: 'Build and manage backend services and APIs.',
},
- {
- type: 'category',
- label: 'Building Plugins & Modules',
- items: [
- 'backend-system/building-plugins-and-modules/index',
- 'backend-system/building-plugins-and-modules/testing',
- 'backend-system/building-plugins-and-modules/migrating',
- ],
- },
- {
- type: 'category',
- label: 'Core Services',
- items: [
- 'backend-system/core-services/index',
- 'backend-system/core-services/auditor',
- 'backend-system/core-services/auth',
- 'backend-system/core-services/cache',
- 'backend-system/core-services/database',
- 'backend-system/core-services/discovery',
- 'backend-system/core-services/http-auth',
- 'backend-system/core-services/http-router',
- 'backend-system/core-services/identity',
- 'backend-system/core-services/lifecycle',
- 'backend-system/core-services/logger',
- 'backend-system/core-services/permissions',
- 'backend-system/core-services/permissions-registry',
- 'backend-system/core-services/plugin-metadata',
- 'backend-system/core-services/root-config',
- 'backend-system/core-services/root-health',
- 'backend-system/core-services/root-http-router',
- 'backend-system/core-services/root-lifecycle',
- 'backend-system/core-services/root-logger',
- 'backend-system/core-services/scheduler',
- 'backend-system/core-services/token-manager',
- 'backend-system/core-services/url-reader',
- 'backend-system/core-services/user-info',
- 'backend-system/core-services/actions-registry',
- 'backend-system/core-services/actions',
- ],
- },
- ],
- },
+ [
+ 'plugins/proxying',
+ 'plugins/backend-plugin',
+ 'plugins/call-existing-api',
+ ],
+ ),
+ sidebarElementWithIndex(
+ { label: 'Testing', description: 'Testing plugins and modules.' },
+ ['plugins/testing'],
+ ),
+ sidebarElementWithIndex(
+ { label: 'Publishing', description: 'Publishing your plugins.' },
+ ['plugins/publish-private', 'plugins/add-to-directory'],
+ ),
+ 'plugins/observability',
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'New Frontend System',
- items: [
- 'frontend-system/index',
+ label: 'Configuration',
+ description: 'Manage static configuration files and settings.',
+ },
+ ['conf/index', 'conf/reading', 'conf/writing', 'conf/defining'],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Framework',
+ description: 'Core framework concepts and architecture.',
+ },
+ [
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Architecture',
- items: [
- 'frontend-system/architecture/index',
- 'frontend-system/architecture/app',
- '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/migrations',
- ],
+ label: 'Backend System',
+ description: 'Backend system components and architecture.',
},
+ [
+ 'backend-system/index',
+ sidebarElementWithIndex(
+ {
+ label: 'Architecture',
+ description: 'Architecture of the backend system.',
+ },
+ [
+ 'backend-system/architecture/index',
+ 'backend-system/architecture/services',
+ 'backend-system/architecture/plugins',
+ 'backend-system/architecture/extension-points',
+ 'backend-system/architecture/modules',
+ 'backend-system/architecture/feature-loaders',
+ 'backend-system/architecture/naming-patterns',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Building Backends',
+ description: 'Guides on building backend systems.',
+ },
+ [
+ 'backend-system/building-backends/index',
+ 'backend-system/building-backends/migrating',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Building Plugins & Modules',
+ description: 'Guides on building plugins and modules.',
+ },
+ [
+ 'backend-system/building-plugins-and-modules/index',
+ 'backend-system/building-plugins-and-modules/testing',
+ 'backend-system/building-plugins-and-modules/migrating',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Core Services',
+ description: 'Core services of the backend system.',
+ },
+ [
+ 'backend-system/core-services/index',
+ 'backend-system/core-services/auditor',
+ 'backend-system/core-services/auth',
+ 'backend-system/core-services/cache',
+ 'backend-system/core-services/database',
+ 'backend-system/core-services/discovery',
+ 'backend-system/core-services/http-auth',
+ 'backend-system/core-services/http-router',
+ 'backend-system/core-services/identity',
+ 'backend-system/core-services/lifecycle',
+ 'backend-system/core-services/logger',
+ 'backend-system/core-services/permissions',
+ 'backend-system/core-services/permissions-registry',
+ 'backend-system/core-services/plugin-metadata',
+ 'backend-system/core-services/root-config',
+ 'backend-system/core-services/root-health',
+ 'backend-system/core-services/root-http-router',
+ 'backend-system/core-services/root-instance-metadata',
+ 'backend-system/core-services/root-lifecycle',
+ 'backend-system/core-services/root-logger',
+ 'backend-system/core-services/scheduler',
+ 'backend-system/core-services/token-manager',
+ 'backend-system/core-services/url-reader',
+ 'backend-system/core-services/user-info',
+ 'backend-system/core-services/actions-registry',
+ 'backend-system/core-services/actions',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Building Plugins',
- items: [
- 'frontend-system/building-plugins/index',
- 'frontend-system/building-plugins/testing',
- 'frontend-system/building-plugins/common-extension-blueprints',
- 'frontend-system/building-plugins/built-in-data-refs',
- 'frontend-system/building-plugins/migrating',
- ],
+ label: 'New Frontend System',
+ description: 'New frontend system components and architecture.',
},
+ [
+ 'frontend-system/index',
+ sidebarElementWithIndex(
+ {
+ label: 'Architecture',
+ description: 'Architecture of the new frontend system.',
+ },
+ [
+ 'frontend-system/architecture/index',
+ 'frontend-system/architecture/app',
+ '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/migrations',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Building Plugins',
+ description: 'Guides on building frontend plugins.',
+ },
+ [
+ 'frontend-system/building-plugins/index',
+ 'frontend-system/building-plugins/testing',
+ 'frontend-system/building-plugins/common-extension-blueprints',
+ 'frontend-system/building-plugins/built-in-data-refs',
+ 'frontend-system/building-plugins/migrating',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Building Apps',
+ description: 'Guides on building frontend applications.',
+ },
+ [
+ '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',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Utility APIs',
+ description: 'Utility APIs for the frontend system.',
+ },
+ [
+ 'frontend-system/utility-apis/index',
+ 'frontend-system/utility-apis/creating',
+ 'frontend-system/utility-apis/consuming',
+ 'frontend-system/utility-apis/configuring',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Building Apps',
- items: [
- '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',
- ],
+ label: 'Backstage CLI',
+ description: 'Command-line interface for Backstage.',
},
+ [
+ 'tooling/cli/overview',
+ 'tooling/cli/build-system',
+ 'tooling/cli/commands',
+ 'tooling/cli/templates',
+ sidebarElementWithIndex(
+ {
+ label: 'Local Development',
+ description:
+ 'Guides for local development using Backstage CLI.',
+ },
+ [
+ 'tooling/local-dev/linking-local-packages',
+ 'tooling/local-dev/debugging',
+ 'tooling/local-dev/profiling',
+ ],
+ ),
+ 'tooling/package-metadata',
+ ],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Utility APIs',
- items: [
- 'frontend-system/utility-apis/index',
- 'frontend-system/utility-apis/creating',
- 'frontend-system/utility-apis/consuming',
- 'frontend-system/utility-apis/configuring',
- ],
+ label: 'User Interface',
+ description: 'User interface customization and configuration.',
},
- ],
- },
+ [
+ 'conf/user-interface/index',
+ 'conf/user-interface/logo',
+ 'conf/user-interface/icons',
+ 'conf/user-interface/sidebar',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
{
- 'Backstage CLI': [
- 'tooling/cli/overview',
- 'tooling/cli/build-system',
- 'tooling/cli/commands',
- 'tooling/cli/templates',
+ label: 'Tutorials',
+ description:
+ 'Step-by-step tutorials for various Backstage features and use cases.',
+ },
+ [
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'Local Development',
- items: [
- 'tooling/local-dev/linking-local-packages',
- 'tooling/local-dev/debugging',
- 'tooling/local-dev/profiling',
- ],
+ label: 'Non-technical',
+ description: 'Non-technical tutorials and guides.',
},
- 'tooling/package-metadata',
- ],
- },
+ ['overview/adopting'],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Technical',
+ description: 'Technical tutorials and guides.',
+ },
+ [
+ 'tutorials/quickstart-app-plugin',
+ 'tutorials/configuring-plugin-databases',
+ 'tutorials/manual-knex-rollback',
+ 'tutorials/switching-sqlite-postgres',
+ 'tutorials/using-backstage-proxy-within-plugin',
+ 'tutorials/enable-public-entry',
+ 'tutorials/setup-opentelemetry',
+ 'tutorials/integrating-event-driven-updates-with-entity-providers',
+ 'accessibility/index',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Migrations',
+ description:
+ 'Guides for migrating between different versions and systems.',
+ },
+ [
+ 'tutorials/jest30-migration',
+ 'tutorials/react-router-stable-migration',
+ 'tutorials/react18-migration',
+ 'tutorials/package-role-migration',
+ 'tutorials/migrating-away-from-core',
+ 'tutorials/yarn-migration',
+ 'tutorials/migrate-to-mui5',
+ 'tutorials/auth-service-migration',
+ 'tutorials/jsx-transform-migration',
+ ],
+ ),
+ ],
+ ),
+ sidebarElementWithIndex(
+ { label: 'FAQ', description: 'Frequently asked questions and answers.' },
+ ['faq/index', 'faq/product', 'faq/technical'],
+ ),
+ sidebarElementWithIndex(
{
- type: 'category',
- label: 'User Interface',
- items: [
- 'conf/user-interface/index',
- 'conf/user-interface/logo',
- 'conf/user-interface/icons',
- 'conf/user-interface/sidebar',
- ],
+ label: 'Contribute',
+ description: 'Information on contributing to Backstage.',
},
- ],
- Tutorials: [
- { 'Non-technical': ['overview/adopting'] },
+ [
+ 'contribute/index',
+ 'contribute/getting-involved',
+ 'contribute/project-structure',
+ ],
+ ),
+ sidebarElementWithIndex(
{
- Technical: [
- 'tutorials/quickstart-app-plugin',
- 'tutorials/configuring-plugin-databases',
- 'tutorials/manual-knex-rollback',
- 'tutorials/switching-sqlite-postgres',
- 'tutorials/using-backstage-proxy-within-plugin',
- 'tutorials/enable-public-entry',
- 'tutorials/setup-opentelemetry',
- 'tutorials/integrating-event-driven-updates-with-entity-providers',
- 'accessibility/index',
- ],
+ label: 'References',
+ description: 'Reference materials and documentation.',
},
- {
- Migrations: [
- 'tutorials/react-router-stable-migration',
- 'tutorials/react18-migration',
- 'tutorials/package-role-migration',
- 'tutorials/migrating-away-from-core',
- 'tutorials/yarn-migration',
- 'tutorials/migrate-to-mui5',
- 'tutorials/auth-service-migration',
- 'tutorials/jsx-transform-migration',
- ],
- },
- ],
- FAQ: ['faq/index', 'faq/product', 'faq/technical'],
- Contribute: [
- 'contribute/index',
- 'contribute/getting-involved',
- 'contribute/project-structure',
- ],
- References: [
- {
- 'Designing for Backstage': [
- 'dls/design',
- 'dls/component-design-guidelines',
- 'dls/contributing-to-storybook',
- 'dls/figma',
- ],
- },
- {
- type: 'category',
- label: 'Architecture Decision Records (ADRs)',
- items: [
- 'architecture-decisions/adrs-overview',
- 'architecture-decisions/adrs-adr001',
- 'architecture-decisions/adrs-adr002',
- 'architecture-decisions/adrs-adr003',
- 'architecture-decisions/adrs-adr004',
- 'architecture-decisions/adrs-adr005',
- 'architecture-decisions/adrs-adr006',
- 'architecture-decisions/adrs-adr007',
- 'architecture-decisions/adrs-adr008',
- 'architecture-decisions/adrs-adr009',
- 'architecture-decisions/adrs-adr010',
- 'architecture-decisions/adrs-adr011',
- 'architecture-decisions/adrs-adr012',
- 'architecture-decisions/adrs-adr013',
- 'architecture-decisions/adrs-adr014',
- ],
- },
- 'api/deprecations',
- 'references/glossary',
- 'api/utility-apis',
- 'reference/index',
- ],
- },
+ [
+ sidebarElementWithIndex(
+ {
+ label: 'Designing for Backstage',
+ description:
+ 'Guidelines and resources for designing Backstage components.',
+ },
+ [
+ 'dls/design',
+ 'dls/component-design-guidelines',
+ 'dls/contributing-to-storybook',
+ 'dls/figma',
+ ],
+ ),
+ sidebarElementWithIndex(
+ {
+ label: 'Architecture Decision Records (ADRs)',
+ description:
+ 'Documentation of architectural decisions made in Backstage.',
+ },
+ [
+ 'architecture-decisions/adrs-overview',
+ 'architecture-decisions/adrs-adr001',
+ 'architecture-decisions/adrs-adr002',
+ 'architecture-decisions/adrs-adr003',
+ 'architecture-decisions/adrs-adr004',
+ 'architecture-decisions/adrs-adr005',
+ 'architecture-decisions/adrs-adr006',
+ 'architecture-decisions/adrs-adr007',
+ 'architecture-decisions/adrs-adr008',
+ 'architecture-decisions/adrs-adr009',
+ 'architecture-decisions/adrs-adr010',
+ 'architecture-decisions/adrs-adr011',
+ 'architecture-decisions/adrs-adr012',
+ 'architecture-decisions/adrs-adr013',
+ 'architecture-decisions/adrs-adr014',
+ 'architecture-decisions/adrs-adr015',
+ ],
+ ),
+ 'api/deprecations',
+ 'references/glossary',
+ 'api/utility-apis',
+ 'reference/index',
+ ],
+ ),
+ ],
releases: {
'Release Notes': releases.map(release => `releases/${release}`),
},
diff --git a/microsite/static/img/citrix_sds.png b/microsite/static/img/citrix_sds.png
new file mode 100644
index 0000000000..fac58411a1
Binary files /dev/null and b/microsite/static/img/citrix_sds.png differ
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 2f6e723889..acceac04e2 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -2961,90 +2961,90 @@ __metadata:
languageName: node
linkType: hard
-"@swc/core-darwin-arm64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-darwin-arm64@npm:1.15.3"
+"@swc/core-darwin-arm64@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-darwin-arm64@npm:1.15.6"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-darwin-x64@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-darwin-x64@npm:1.15.3"
+"@swc/core-darwin-x64@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-darwin-x64@npm:1.15.6"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@swc/core-linux-arm-gnueabihf@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.3"
+"@swc/core-linux-arm-gnueabihf@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.6"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@swc/core-linux-arm64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm64-gnu@npm:1.15.3"
+"@swc/core-linux-arm64-gnu@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-linux-arm64-gnu@npm:1.15.6"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-arm64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-arm64-musl@npm:1.15.3"
+"@swc/core-linux-arm64-musl@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-linux-arm64-musl@npm:1.15.6"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-linux-x64-gnu@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-x64-gnu@npm:1.15.3"
+"@swc/core-linux-x64-gnu@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-linux-x64-gnu@npm:1.15.6"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@swc/core-linux-x64-musl@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-linux-x64-musl@npm:1.15.3"
+"@swc/core-linux-x64-musl@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-linux-x64-musl@npm:1.15.6"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@swc/core-win32-arm64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-arm64-msvc@npm:1.15.3"
+"@swc/core-win32-arm64-msvc@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-win32-arm64-msvc@npm:1.15.6"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@swc/core-win32-ia32-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-ia32-msvc@npm:1.15.3"
+"@swc/core-win32-ia32-msvc@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-win32-ia32-msvc@npm:1.15.6"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@swc/core-win32-x64-msvc@npm:1.15.3":
- version: 1.15.3
- resolution: "@swc/core-win32-x64-msvc@npm:1.15.3"
+"@swc/core-win32-x64-msvc@npm:1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core-win32-x64-msvc@npm:1.15.6"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@swc/core@npm:^1.3.46":
- version: 1.15.3
- resolution: "@swc/core@npm:1.15.3"
+"@swc/core@npm:^1.15.6":
+ version: 1.15.6
+ resolution: "@swc/core@npm:1.15.6"
dependencies:
- "@swc/core-darwin-arm64": "npm:1.15.3"
- "@swc/core-darwin-x64": "npm:1.15.3"
- "@swc/core-linux-arm-gnueabihf": "npm:1.15.3"
- "@swc/core-linux-arm64-gnu": "npm:1.15.3"
- "@swc/core-linux-arm64-musl": "npm:1.15.3"
- "@swc/core-linux-x64-gnu": "npm:1.15.3"
- "@swc/core-linux-x64-musl": "npm:1.15.3"
- "@swc/core-win32-arm64-msvc": "npm:1.15.3"
- "@swc/core-win32-ia32-msvc": "npm:1.15.3"
- "@swc/core-win32-x64-msvc": "npm:1.15.3"
+ "@swc/core-darwin-arm64": "npm:1.15.6"
+ "@swc/core-darwin-x64": "npm:1.15.6"
+ "@swc/core-linux-arm-gnueabihf": "npm:1.15.6"
+ "@swc/core-linux-arm64-gnu": "npm:1.15.6"
+ "@swc/core-linux-arm64-musl": "npm:1.15.6"
+ "@swc/core-linux-x64-gnu": "npm:1.15.6"
+ "@swc/core-linux-x64-musl": "npm:1.15.6"
+ "@swc/core-win32-arm64-msvc": "npm:1.15.6"
+ "@swc/core-win32-ia32-msvc": "npm:1.15.6"
+ "@swc/core-win32-x64-msvc": "npm:1.15.6"
"@swc/counter": "npm:^0.1.3"
"@swc/types": "npm:^0.1.25"
peerDependencies:
@@ -3073,7 +3073,7 @@ __metadata:
peerDependenciesMeta:
"@swc/helpers":
optional: true
- checksum: 10/280330d82328818138ed64fdcf9ea9abde6b6f16eca65a9d4db27dde06a8dfffd2649f3447d2243387277513c7430fa4142cafcfd64e943d682ce6a713cb8c2d
+ checksum: 10/d17d1b5e6e81f8c262b668f237b863dd612eaa19fc35e74067b2a27ba3998c0c511ec7f49cd91c02f61fa77dd1b5d7e65d293736f65ec45d6c13989904b1c7eb
languageName: node
linkType: hard
@@ -4274,7 +4274,7 @@ __metadata:
"@docusaurus/tsconfig": "npm:^3.1.1"
"@docusaurus/types": "npm:^3.1.1"
"@mdx-js/react": "npm:^3.0.0"
- "@swc/core": "npm:^1.3.46"
+ "@swc/core": "npm:^1.15.6"
"@types/webpack-env": "npm:^1.18.0"
clsx: "npm:^2.0.0"
docusaurus-plugin-openapi-docs: "npm:^4.3.0"
@@ -4721,9 +4721,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688":
- version: 1.0.30001695
- resolution: "caniuse-lite@npm:1.0.30001695"
- checksum: 10/8107c5e89b86c7a2fd506b93c658ff945c98c6518260c3b28af9f02bd83bf83939696241f0b413545c5b9895c86bcae64c9370388576440e74e9b848f04170d3
+ version: 1.0.30001760
+ resolution: "caniuse-lite@npm:1.0.30001760"
+ checksum: 10/ac5c13d00b946c3ace331d25379ed9d85743b1028aba79ae80402cb128b4b491122585144898fefb836d4d9879c13c717a081ae241a00420cbbc7e1b934ec685
languageName: node
linkType: hard
diff --git a/package.json b/package.json
index 4a67751584..879899cae1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "root",
- "version": "1.46.0-next.1",
+ "version": "1.47.0-next.0",
"backstage": {
"cli": {
"new": {
@@ -117,7 +117,6 @@
"csstype@npm:^3.0.2": "3.0.9",
"csstype@npm:^3.1.2": "3.0.9",
"csstype@npm:^3.1.3": "3.0.9",
- "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch",
"recast@npm:0.23.9>ast-types": "patch:ast-types@npm%3A0.16.1#./.yarn/patches/ast-types-npm-0.16.1-43c4ac4b0d.patch"
},
"dependencies": {
@@ -138,11 +137,12 @@
"@octokit/rest": "^19.0.3",
"@playwright/test": "^1.32.3",
"@spotify/eslint-plugin": "^15.0.0",
- "@storybook/addon-a11y": "^9.1.7",
- "@storybook/addon-docs": "^9.1.7",
- "@storybook/addon-links": "^9.1.7",
- "@storybook/addon-themes": "^9.1.7",
- "@storybook/react-vite": "^9.1.7",
+ "@storybook/addon-a11y": "^10.1.9",
+ "@storybook/addon-docs": "^10.1.9",
+ "@storybook/addon-links": "^10.1.9",
+ "@storybook/addon-themes": "^10.1.9",
+ "@storybook/addon-vitest": "^10.1.9",
+ "@storybook/react-vite": "^10.1.9",
"@techdocs/cli": "workspace:*",
"@types/cacheable-request": "^8.3.6",
"@types/memjs": "^1.3.3",
@@ -168,7 +168,7 @@
"shx": "^0.4.0",
"sloc": "^0.3.1",
"sort-package-json": "^2.8.0",
- "storybook": "^9.1.7",
+ "storybook": "^10.1.10",
"ts-morph": "^24.0.0",
"typedoc": "^0.28.0",
"typescript": "~5.7.0",
diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md
index ee64c2209e..cca956654d 100644
--- a/packages/app-defaults/CHANGELOG.md
+++ b/packages/app-defaults/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/app-defaults
+## 1.7.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-permission-react@0.4.39
+
## 1.7.3-next.0
### Patch Changes
diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json
index ec08c04929..878e5b505b 100644
--- a/packages/app-defaults/package.json
+++ b/packages/app-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/app-defaults",
- "version": "1.7.3-next.0",
+ "version": "1.7.3",
"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 da46f90c56..ee1e797be4 100644
--- a/packages/app-next-example-plugin/CHANGELOG.md
+++ b/packages/app-next-example-plugin/CHANGELOG.md
@@ -1,5 +1,13 @@
# app-next-example-plugin
+## 0.0.30
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+
## 0.0.30-next.0
### Patch Changes
diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json
index f7d113827d..f632f00de8 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.30-next.0",
+ "version": "0.0.30",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md
index 9e29cd2b2e..d0789288e3 100644
--- a/packages/app-next/CHANGELOG.md
+++ b/packages/app-next/CHANGELOG.md
@@ -1,5 +1,142 @@
# example-app-next
+## 0.0.31-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/cli@0.35.2-next.0
+ - @backstage/plugin-app-visualizer@0.1.27-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/frontend-defaults@0.3.4
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/integration-react@1.2.13
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-app@0.3.3
+ - @backstage/plugin-auth@0.1.3
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-home@0.8.15
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-techdocs-react@1.3.6
+ - @backstage/plugin-user-settings@0.8.30
+
+## 0.0.30
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/cli@0.35.0
+ - @backstage/ui@0.10.0
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-app@0.3.3
+ - @backstage/plugin-org@0.6.47
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-app-visualizer@0.1.26
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-user-settings@0.8.30
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-home@0.8.15
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/app-defaults@1.7.3
+ - @backstage/core-compat-api@0.5.5
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/frontend-defaults@0.3.4
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-auth@0.1.3
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## 0.0.30-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/cli@0.35.0-next.2
+ - @backstage/plugin-catalog-graph@0.5.4-next.1
+ - @backstage/plugin-api-docs@0.13.2-next.1
+ - @backstage/plugin-techdocs@1.16.1-next.2
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/plugin-search@1.5.1-next.1
+ - @backstage/plugin-app@0.3.3-next.1
+ - @backstage/plugin-org@0.6.47-next.1
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home@0.8.15-next.1
+ - @backstage/plugin-scaffolder@1.34.4-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32-next.2
+ - @backstage/app-defaults@1.7.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-compat-api@0.5.5-next.0
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/frontend-app-api@0.13.3-next.0
+ - @backstage/frontend-defaults@0.3.4-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/ui@0.10.0-next.1
+ - @backstage/plugin-app-visualizer@0.1.26-next.1
+ - @backstage/plugin-auth@0.1.3-next.0
+ - @backstage/plugin-auth-react@0.1.22-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-import@0.13.8-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1
+ - @backstage/plugin-kubernetes@0.12.14-next.1
+ - @backstage/plugin-notifications@0.5.12-next.0
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-signals@0.0.26-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+ - @backstage/plugin-user-settings@0.8.30-next.0
+
## 0.0.30-next.1
### Patch Changes
diff --git a/packages/app-next/package.json b/packages/app-next/package.json
index 2a3b5dbf8a..e1709b4c8f 100644
--- a/packages/app-next/package.json
+++ b/packages/app-next/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app-next",
- "version": "0.0.30-next.1",
+ "version": "0.0.31-next.0",
"backstage": {
"role": "frontend"
},
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 82e800c5ef..cc51e2878c 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,130 @@
# example-app
+## 0.2.117-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/cli@0.35.2-next.0
+ - @backstage/plugin-mui-to-bui@0.2.3-next.0
+ - @backstage/app-defaults@1.7.3
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-app-api@1.19.3
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/integration-react@1.2.13
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-devtools@0.1.34
+ - @backstage/plugin-home@0.8.15
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-techdocs-react@1.3.6
+ - @backstage/plugin-user-settings@0.8.30
+
+## 0.2.116
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/cli@0.35.0
+ - @backstage/ui@0.10.0
+ - @backstage/plugin-catalog-graph@0.5.4
+ - @backstage/plugin-api-docs@0.13.2
+ - @backstage/plugin-techdocs@1.16.1
+ - @backstage/plugin-catalog@1.32.1
+ - @backstage/plugin-search@1.5.1
+ - @backstage/plugin-org@0.6.47
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes@0.12.14
+ - @backstage/plugin-search-react@1.10.1
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24
+ - @backstage/plugin-catalog-import@0.13.8
+ - @backstage/plugin-notifications@0.5.12
+ - @backstage/plugin-user-settings@0.8.30
+ - @backstage/plugin-scaffolder@1.35.0
+ - @backstage/plugin-devtools@0.1.34
+ - @backstage/plugin-home@0.8.15
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32
+ - @backstage/app-defaults@1.7.3
+ - @backstage/frontend-app-api@0.13.3
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-auth-react@0.1.22
+ - @backstage/plugin-mui-to-bui@0.2.2
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-signals@0.0.26
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## 0.2.116-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/cli@0.35.0-next.2
+ - @backstage/plugin-catalog-graph@0.5.4-next.1
+ - @backstage/plugin-api-docs@0.13.2-next.1
+ - @backstage/plugin-techdocs@1.16.1-next.2
+ - @backstage/plugin-catalog@1.32.1-next.1
+ - @backstage/plugin-search@1.5.1-next.1
+ - @backstage/plugin-org@0.6.47-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home@0.8.15-next.1
+ - @backstage/plugin-scaffolder@1.34.4-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.32-next.2
+ - @backstage/app-defaults@1.7.3-next.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/frontend-app-api@0.13.3-next.0
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/ui@0.10.0-next.1
+ - @backstage/plugin-auth-react@0.1.22-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-catalog-import@0.13.8-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1
+ - @backstage/plugin-devtools@0.1.34-next.0
+ - @backstage/plugin-kubernetes@0.12.14-next.1
+ - @backstage/plugin-mui-to-bui@0.2.2-next.1
+ - @backstage/plugin-notifications@0.5.12-next.0
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-search-common@1.2.21
+ - @backstage/plugin-search-react@1.10.1-next.0
+ - @backstage/plugin-signals@0.0.26-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+ - @backstage/plugin-user-settings@0.8.30-next.0
+
## 0.2.116-next.1
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 5515e2a9c7..b2464821c0 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app",
- "version": "0.2.116-next.1",
+ "version": "0.2.117-next.0",
"backstage": {
"role": "frontend"
},
diff --git a/packages/app/src/components/devtools/CustomDevToolsPage.tsx b/packages/app/src/components/devtools/CustomDevToolsPage.tsx
index d1c68d9884..0fe17b14c3 100644
--- a/packages/app/src/components/devtools/CustomDevToolsPage.tsx
+++ b/packages/app/src/components/devtools/CustomDevToolsPage.tsx
@@ -18,6 +18,7 @@ import {
ConfigContent,
ExternalDependenciesContent,
InfoContent,
+ ScheduledTasksContent,
} from '@backstage/plugin-devtools';
import { DevToolsLayout } from '@backstage/plugin-devtools';
import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities';
@@ -31,6 +32,9 @@ const DevToolsPage = () => {
+
+
+ {
it('correctly applies namespace configuration to redis and valkey stores', () => {
const testCases = [
- { store: 'redis', namespace: 'test1', separator: ':' },
- { store: 'valkey', namespace: 'test2', separator: '!' },
+ {
+ store: 'redis',
+ client: {
+ namespace: 'my-app',
+ keyPrefixSeparator: ':',
+ },
+ },
+ { store: 'valkey', client: { keyPrefix: 'my-app:' } },
];
- testCases.forEach(({ store, namespace, separator }) => {
+ testCases.forEach(({ store, client }) => {
const manager = CacheManager.fromConfig(
mockServices.rootConfig({
data: {
@@ -349,10 +355,7 @@ describe('CacheManager store options', () => {
store,
connection: 'redis://localhost:6379',
[store]: {
- client: {
- namespace,
- keyPrefixSeparator: separator,
- },
+ client,
},
},
},
@@ -364,16 +367,16 @@ describe('CacheManager store options', () => {
if (store === 'redis') {
// eslint-disable-next-line jest/no-conditional-expect
- expect(KeyvRedis).toHaveBeenCalledWith('redis://localhost:6379', {
- namespace,
- keyPrefixSeparator: separator,
- });
+ expect(KeyvRedis).toHaveBeenCalledWith(
+ 'redis://localhost:6379',
+ client,
+ );
} else if (store === 'valkey') {
// eslint-disable-next-line jest/no-conditional-expect
- expect(KeyvValkey).toHaveBeenCalledWith('redis://localhost:6379', {
- namespace,
- keyPrefixSeparator: separator,
- });
+ expect(KeyvValkey).toHaveBeenCalledWith(
+ 'redis://localhost:6379',
+ client,
+ );
}
});
});
@@ -408,21 +411,31 @@ describe('CacheManager store options', () => {
expect(result).toBe('testPlugin');
});
- it('returns pluginId when store options have no namespace', () => {
- const storeOptions = {
- client: {
- keyPrefixSeparator: ':',
- },
- };
- const result = (CacheManager as any).constructNamespace(
- 'testPlugin',
- storeOptions,
- );
- expect(result).toBe('testPlugin');
- });
+ it.each([
+ {
+ type: 'redis',
+ field: 'namespace',
+ client: { keyPrefixSeparator: ':' },
+ },
+ { type: 'valkey', field: 'keyPrefix', client: {} },
+ ])(
+ 'returns pluginId when store options have no $field for $type',
+ ({ type, client }) => {
+ const storeOptions = {
+ type,
+ client,
+ };
+ const result = (CacheManager as any).constructNamespace(
+ 'testPlugin',
+ storeOptions,
+ );
+ expect(result).toBe('testPlugin');
+ },
+ );
- it('combines namespace and pluginId with default separator', () => {
+ it('combines namespace and pluginId with default separator for redis', () => {
const storeOptions = {
+ type: 'redis',
client: {
namespace: 'my-app',
keyPrefixSeparator: ':',
@@ -435,8 +448,9 @@ describe('CacheManager store options', () => {
expect(result).toBe('my-app:testPlugin');
});
- it('combines namespace and pluginId with custom separator', () => {
+ it('combines namespace and pluginId with custom separator for redis', () => {
const storeOptions = {
+ type: 'redis',
client: {
namespace: 'my-app',
keyPrefixSeparator: '-',
@@ -449,8 +463,9 @@ describe('CacheManager store options', () => {
expect(result).toBe('my-app-testPlugin');
});
- it('uses default separator when keyPrefixSeparator is not provided', () => {
+ it('uses default separator when keyPrefixSeparator is not provided for redis', () => {
const storeOptions = {
+ type: 'redis',
client: {
namespace: 'my-app',
},
@@ -462,6 +477,20 @@ describe('CacheManager store options', () => {
expect(result).toBe('my-app:testPlugin');
});
+ it('uses keyPrefix for valkey', () => {
+ const storeOptions = {
+ type: 'valkey',
+ client: {
+ keyPrefix: 'my-app:',
+ },
+ };
+ const result = (CacheManager as any).constructNamespace(
+ 'testPlugin',
+ storeOptions,
+ );
+ expect(result).toBe('my-app:testPlugin');
+ });
+
it('handles empty namespace by falling back to pluginId', () => {
const storeOptions = {
client: {
diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
index 06b2b0c57b..38c0c761ed 100644
--- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
+++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
@@ -29,6 +29,7 @@ import {
RedisCacheStoreOptions,
InfinispanClientBehaviorOptions,
InfinispanServerConfig,
+ ValkeyCacheStoreOptions,
} from './types';
import { InfinispanOptionsMapper } from './providers/infinispan/InfinispanOptionsMapper';
import { durationToMilliseconds } from '@backstage/types';
@@ -134,43 +135,38 @@ export class CacheManager {
): CacheStoreOptions | undefined {
const storeConfigPath = `backend.cache.${store}`;
- if (!config.has(storeConfigPath)) {
+ if (store !== 'memory' && !config.has(storeConfigPath)) {
logger?.warn(
`No configuration found for cache store '${store}' at '${storeConfigPath}'.`,
);
}
- if (store === 'redis' || store === 'valkey') {
- return CacheManager.parseRedisOptions(
- store,
- storeConfigPath,
- config,
- logger,
- );
+ switch (store) {
+ case 'redis':
+ return CacheManager.parseRedisOptions(storeConfigPath, config, logger);
+ case 'valkey':
+ return CacheManager.parseValkeyOptions(storeConfigPath, config, logger);
+ case 'infinispan':
+ return InfinispanOptionsMapper.parseInfinispanOptions(
+ storeConfigPath,
+ config,
+ logger,
+ );
+ default:
+ return undefined;
}
-
- if (store === 'infinispan') {
- return InfinispanOptionsMapper.parseInfinispanOptions(
- storeConfigPath,
- config,
- logger,
- );
- }
-
- return undefined;
}
/**
* Parse Redis-specific options from configuration.
*/
private static parseRedisOptions(
- store: string,
storeConfigPath: string,
config: RootConfigService,
logger?: LoggerService,
): RedisCacheStoreOptions {
const redisOptions: RedisCacheStoreOptions = {
- type: store as 'redis' | 'valkey',
+ type: 'redis',
};
const redisConfig =
@@ -213,6 +209,51 @@ export class CacheManager {
return redisOptions;
}
+ /**
+ * Parse Valkey-specific options from configuration.
+ */
+ private static parseValkeyOptions(
+ storeConfigPath: string,
+ config: RootConfigService,
+ logger?: LoggerService,
+ ): ValkeyCacheStoreOptions {
+ const valkeyOptions: ValkeyCacheStoreOptions = {
+ type: 'valkey',
+ };
+
+ const valkeyConfig =
+ config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({});
+
+ valkeyOptions.client = {
+ keyPrefix: valkeyConfig.getOptionalString('client.keyPrefix'),
+ };
+
+ if (valkeyConfig.has('cluster')) {
+ const clusterConfig = valkeyConfig.getConfig('cluster');
+
+ if (!clusterConfig.has('rootNodes')) {
+ logger?.warn(
+ `Valkey cluster config has no 'rootNodes' key, defaulting to non-clustered mode`,
+ );
+ return valkeyOptions;
+ }
+
+ valkeyOptions.cluster = {
+ rootNodes: clusterConfig.get('rootNodes'),
+ defaults: clusterConfig.getOptional('defaults'),
+ minimizeConnections: clusterConfig.getOptionalBoolean(
+ 'minimizeConnections',
+ ),
+ useReplicas: clusterConfig.getOptionalBoolean('useReplicas'),
+ maxCommandRedirections: clusterConfig.getOptionalNumber(
+ 'maxCommandRedirections',
+ ),
+ };
+ }
+
+ return valkeyOptions;
+ }
+
/**
* Construct the full namespace based on the options and pluginId.
*
@@ -222,13 +263,23 @@ export class CacheManager {
*/
private static constructNamespace(
pluginId: string,
- storeOptions: RedisCacheStoreOptions | undefined,
+ storeOptions: RedisCacheStoreOptions | ValkeyCacheStoreOptions | undefined,
): string {
- const prefix = storeOptions?.client?.namespace
- ? `${storeOptions.client.namespace}${
- storeOptions.client.keyPrefixSeparator ?? ':'
- }`
- : '';
+ let prefix: string;
+ switch (storeOptions?.type) {
+ case 'redis':
+ prefix = storeOptions?.client?.namespace
+ ? `${storeOptions.client.namespace}${
+ storeOptions.client.keyPrefixSeparator ?? ':'
+ }`
+ : '';
+ break;
+ case 'valkey':
+ prefix = storeOptions.client?.keyPrefix ?? '';
+ break;
+ default:
+ prefix = '';
+ }
return `${prefix}${pluginId}`;
}
@@ -317,7 +368,9 @@ export class CacheManager {
private createValkeyStoreFactory(): StoreFactory {
const KeyvValkey = require('@keyv/valkey').default;
- const { createCluster } = require('@keyv/valkey');
+ // `@keyv/valkey` doesn't export a `createCluster` function, but is compatible with the one from `@keyv/redis`
+ // See https://keyv.org/docs/storage-adapters/valkey
+ const { createCluster } = require('@keyv/redis');
const stores: Record = {};
return (pluginId, defaultTtl) => {
@@ -327,9 +380,7 @@ export class CacheManager {
);
}
if (!stores[pluginId]) {
- const valkeyOptions = this.storeOptions?.client || {
- keyPrefixSeparator: ':',
- };
+ const valkeyOptions = this.storeOptions?.client;
if (this.storeOptions?.cluster) {
// Create a Valkey cluster (Redis cluster under the hood)
const cluster = createCluster(this.storeOptions?.cluster);
diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts
index 522ab2ac3e..60e32e8618 100644
--- a/packages/backend-defaults/src/entrypoints/cache/types.ts
+++ b/packages/backend-defaults/src/entrypoints/cache/types.ts
@@ -17,6 +17,7 @@
import { LoggerService } from '@backstage/backend-plugin-api';
import { HumanDuration, durationToMilliseconds } from '@backstage/types';
import { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis';
+import { KeyvValkeyOptions } from '@keyv/valkey';
/**
* Options for Redis cache store.
@@ -24,11 +25,22 @@ import { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis';
* @public
*/
export type RedisCacheStoreOptions = {
- type: 'redis' | 'valkey';
+ type: 'redis';
client?: KeyvRedisOptions;
cluster?: RedisClusterOptions;
};
+/**
+ * Options for Valkey cache store.
+ *
+ * @public
+ */
+export type ValkeyCacheStoreOptions = {
+ type: 'valkey';
+ client?: KeyvValkeyOptions;
+ cluster?: RedisClusterOptions;
+};
+
/**
* Union type of all cache store options.
*
@@ -36,6 +48,7 @@ export type RedisCacheStoreOptions = {
*/
export type CacheStoreOptions =
| RedisCacheStoreOptions
+ | ValkeyCacheStoreOptions
| InfinispanCacheStoreOptions;
/**
diff --git a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts
index ca9085273c..4a5db18f6f 100644
--- a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts
+++ b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts
@@ -69,7 +69,7 @@ describe('DefaultRootHealthService', () => {
await expect(service.getReadiness()).resolves.toEqual({
status: 503,
payload: {
- message: 'Backend is shuttting down',
+ message: 'Backend is shutting down',
status: 'error',
},
});
diff --git a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts
index e7255610bc..52812fc6b1 100644
--- a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts
+++ b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts
@@ -50,7 +50,7 @@ export class DefaultRootHealthService implements RootHealthService {
if (this.#state === 'down') {
return {
status: 503,
- payload: { message: 'Backend is shuttting down', status: 'error' },
+ payload: { message: 'Backend is shutting down', status: 'error' },
};
}
diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts
index 8f8d1b7656..e9982bc589 100644
--- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts
+++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts
@@ -285,7 +285,7 @@ describe('rootHttpRouterServiceFactory', () => {
// Immediately start failing the readiness health check
await request(app).get('/.backstage/health/v1/readiness').expect(503, {
- message: 'Backend is shuttting down',
+ message: 'Backend is shutting down',
status: 'error',
});
@@ -301,7 +301,7 @@ describe('rootHttpRouterServiceFactory', () => {
.expect(200, { status: 'ok' });
await request(app).get('/.backstage/health/v1/readiness').expect(503, {
- message: 'Backend is shuttting down',
+ message: 'Backend is shutting down',
status: 'error',
});
@@ -312,7 +312,7 @@ describe('rootHttpRouterServiceFactory', () => {
.expect(200, { status: 'ok' });
await request(app).get('/.backstage/health/v1/readiness').expect(503, {
- message: 'Backend is shuttting down',
+ message: 'Backend is shutting down',
status: 'error',
});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts
index ff6ec3bb27..73302d37e5 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts
@@ -68,7 +68,15 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
) {
this.integration = integration;
this.deps = deps;
- const { host, username, appPassword, token } = integration.config;
+ const { host, username, appPassword, token, clientId, clientSecret } =
+ integration.config;
+
+ // Validate: OAuth requires both clientId and clientSecret
+ if ((clientId && !clientSecret) || (clientSecret && !clientId)) {
+ throw new Error(
+ `Bitbucket Cloud integration has incomplete OAuth configuration. Both clientId and clientSecret are required.`,
+ );
+ }
if (username && !token && !appPassword) {
throw new Error(
@@ -91,7 +99,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
url,
this.integration.config,
);
- const requestOptions = getBitbucketCloudRequestOptions(
+ const requestOptions = await getBitbucketCloudRequestOptions(
this.integration.config,
);
@@ -149,7 +157,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
);
const archiveResponse = await fetch(
downloadUrl,
- getBitbucketCloudRequestOptions(this.integration.config),
+ await getBitbucketCloudRequestOptions(this.integration.config),
);
if (!archiveResponse.ok) {
const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`;
@@ -250,7 +258,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
const commitsResponse = await fetch(
commitsApiUrl,
- getBitbucketCloudRequestOptions(this.integration.config),
+ await getBitbucketCloudRequestOptions(this.integration.config),
);
if (!commitsResponse.ok) {
const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;
diff --git a/packages/backend-dev-utils/CHANGELOG.md b/packages/backend-dev-utils/CHANGELOG.md
index 81686fb493..d5884588fb 100644
--- a/packages/backend-dev-utils/CHANGELOG.md
+++ b/packages/backend-dev-utils/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/backend-dev-utils
+## 0.1.6
+
+### Patch Changes
+
+- 2bae83a: Internal update for Node.js v24 support.
+
+## 0.1.6-next.0
+
+### Patch Changes
+
+- 2bae83a: Internal update for Node.js v24 support.
+
## 0.1.5
### Patch Changes
diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json
index 4d22c5010f..ba46f4b36e 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.5",
+ "version": "0.1.6",
"backstage": {
"role": "node-library"
},
diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md
index 25ae340cbf..36d55fd8c2 100644
--- a/packages/backend-dynamic-feature-service/CHANGELOG.md
+++ b/packages/backend-dynamic-feature-service/CHANGELOG.md
@@ -1,5 +1,77 @@
# @backstage/backend-dynamic-feature-service
+## 0.7.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/cli-node@0.2.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-app-node@0.1.40
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-events-backend@0.5.10-next.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-search-common@1.2.21
+
+## 0.7.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-app-node@0.1.40
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-events-backend@0.5.9
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.7.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-app-node@0.1.40-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-events-backend@0.5.9-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+ - @backstage/plugin-search-common@1.2.21
+
## 0.7.7-next.0
### Patch Changes
diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json
index b2f2a5902f..e558403e0e 100644
--- a/packages/backend-dynamic-feature-service/package.json
+++ b/packages/backend-dynamic-feature-service/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-dynamic-feature-service",
- "version": "0.7.7-next.0",
+ "version": "0.7.8-next.0",
"description": "Backstage dynamic feature service",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index 7f3a9328ae..f90fecb9cc 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/backend-openapi-utils
+## 0.6.5-next.0
+
+### Patch Changes
+
+- 6678b78: Internal update to use native feature from our request validation library for handling base path determination.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
+## 0.6.4
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.6.4-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 0.6.4-next.0
### Patch Changes
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index 3c4908de61..5efffd1c55 100644
--- a/packages/backend-openapi-utils/package.json
+++ b/packages/backend-openapi-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-openapi-utils",
- "version": "0.6.4-next.0",
+ "version": "0.6.5-next.0",
"description": "OpenAPI typescript support.",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-openapi-utils/src/stub.ts b/packages/backend-openapi-utils/src/stub.ts
index 8ab00c4df4..717e2ae19b 100644
--- a/packages/backend-openapi-utils/src/stub.ts
+++ b/packages/backend-openapi-utils/src/stub.ts
@@ -31,13 +31,6 @@ import { middleware as OpenApiValidator } from 'express-openapi-validator';
import { OPENAPI_SPEC_ROUTE } from './constants';
import { isErrorResult, merge } from 'openapi-merge';
-type PropertyOverrideRequest = Request & {
- [key: symbol]: string;
-};
-
-const baseUrlSymbol = Symbol();
-const originalUrlSymbol = Symbol();
-
function validatorErrorTransformer(): ErrorRequestHandler {
return (error: Error, _: Request, _2: Response, next: NextFunction) => {
next(new InputError(error.message));
@@ -75,30 +68,6 @@ function createRouterWithValidation(
const router = PromiseRouter();
router.use(options?.middleware || getDefaultRouterMiddleware());
- /**
- * Middleware to setup the routing for OpenApiValidator. OpenApiValidator expects `req.originalUrl`
- * and `req.baseUrl` to be the full path. We adjust them here to basically be nothing and then
- * revive the old values in the last function in this method. We could instead update `req.path`
- * but that might affect the routing and I'd rather not.
- *
- * TODO: I opened https://github.com/cdimascio/express-openapi-validator/issues/843
- * to track this on the middleware side, but there was a similar ticket, https://github.com/cdimascio/express-openapi-validator/issues/113
- * that has had minimal activity. If that changes, update this to use a new option on their side.
- */
- router.use((req: Request, _, next) => {
- /**
- * Express typings are weird. They don't recognize PropertyOverrideRequest as a valid
- * Request child and try to overload as PathParams. Just cast it here, since we know
- * what we're doing.
- */
- const customRequest = req as PropertyOverrideRequest;
- customRequest[baseUrlSymbol] = customRequest.baseUrl;
- customRequest.baseUrl = '';
- customRequest[originalUrlSymbol] = customRequest.originalUrl;
- customRequest.originalUrl = customRequest.url;
- next();
- });
-
// TODO: Handle errors by converting from OpenApiValidator errors to known @backstage/errors errors.
router.use(
OpenApiValidator({
@@ -106,6 +75,7 @@ function createRouterWithValidation(
coerceTypes: false,
allowUnknownQueryParameters: false,
},
+ useRequestUrl: true,
ignoreUndocumented: true,
validateResponses: false,
...options?.validatorOptions,
@@ -113,19 +83,6 @@ function createRouterWithValidation(
}),
);
- /**
- * Revert `req.baseUrl` and `req.originalUrl` changes. This ensures that any further usage
- * of these variables will be unchanged.
- */
- router.use((req: Request, _, next) => {
- const customRequest = req as PropertyOverrideRequest;
- customRequest.baseUrl = customRequest[baseUrlSymbol];
- customRequest.originalUrl = customRequest[originalUrlSymbol];
- delete customRequest[baseUrlSymbol];
- delete customRequest[originalUrlSymbol];
- next();
- });
-
// Any errors from the middleware get through here.
router.use(validatorErrorTransformer());
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index 617012d22b..83d63fa97a 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/backend-plugin-api
+## 1.6.0
+
+### Minor Changes
+
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+
+### Patch Changes
+
+- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`.
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/cli-common@0.1.16
+
+## 1.6.0-next.1
+
+### Minor Changes
+
+- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+
## 1.5.1-next.0
### Patch Changes
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index be377d1d12..4867fcd0a4 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": "1.5.1-next.0",
+ "version": "1.6.0",
"description": "Core API used by Backstage backend plugins",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index d8ee338822..ca456b8491 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,52 @@
# @backstage/backend-test-utils
+## 1.10.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+
+## 1.10.2
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 8be23a4: Switched `textextensions` dependency for `text-extensions`.
+- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-app-api@1.4.0
+ - @backstage/backend-plugin-api@1.6.0
+
+## 1.10.2-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 8be23a4: Switched `textextensions` dependency for `text-extensions`.
+- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/backend-app-api@1.4.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+
## 1.10.1-next.0
### Patch Changes
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index cd4f68ead0..1f6df8457d 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": "1.10.1-next.0",
+ "version": "1.10.3-next.0",
"description": "Test helpers library for Backstage backends",
"backstage": {
"role": "node-library"
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index 622c2329b2..6f44aa4093 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,129 @@
# example-backend
+## 0.0.46-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-app-backend@0.5.9
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/plugin-auth-backend-module-github-provider@0.4.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-catalog-backend@3.3.1-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.10-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7
+ - @backstage/plugin-devtools-backend@0.5.13-next.0
+ - @backstage/plugin-events-backend@0.5.10-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7
+ - @backstage/plugin-kubernetes-backend@0.21.0
+ - @backstage/plugin-mcp-actions-backend@0.1.7-next.0
+ - @backstage/plugin-notifications-backend@0.6.1
+ - @backstage/plugin-permission-backend@0.7.7
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-proxy-backend@0.6.9
+ - @backstage/plugin-scaffolder-backend@3.1.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17
+ - @backstage/plugin-search-backend@2.0.10-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.11
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9
+ - @backstage/plugin-search-backend-module-explore@0.3.10
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-signals-backend@0.3.11
+ - @backstage/plugin-techdocs-backend@2.1.4-next.0
+
+## 0.0.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-mcp-actions-backend@0.1.6
+ - @backstage/plugin-scaffolder-backend@3.1.0
+ - @backstage/plugin-search-backend-node@1.4.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/plugin-app-backend@0.5.9
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3
+ - @backstage/plugin-auth-backend@0.25.7
+ - @backstage/plugin-catalog-backend@3.3.0
+ - @backstage/plugin-devtools-backend@0.5.12
+ - @backstage/plugin-events-backend@0.5.9
+ - @backstage/plugin-kubernetes-backend@0.21.0
+ - @backstage/plugin-notifications-backend@0.6.1
+ - @backstage/plugin-permission-backend@0.7.7
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-proxy-backend@0.6.9
+ - @backstage/plugin-search-backend@2.0.9
+ - @backstage/plugin-signals-backend@0.3.11
+ - @backstage/plugin-techdocs-backend@2.1.3
+ - @backstage/plugin-auth-backend-module-github-provider@0.4.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-search-backend-module-explore@0.3.10
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17
+ - @backstage/plugin-search-backend-module-catalog@0.3.11
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9
+
+## 0.0.45-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-mcp-actions-backend@0.1.6-next.1
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/plugin-app-backend@0.5.9-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1
+ - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1
+ - @backstage/plugin-auth-backend@0.25.7-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-catalog-backend@3.2.1-next.1
+ - @backstage/plugin-devtools-backend@0.5.12-next.1
+ - @backstage/plugin-events-backend@0.5.9-next.1
+ - @backstage/plugin-kubernetes-backend@0.21.0-next.2
+ - @backstage/plugin-notifications-backend@0.6.1-next.1
+ - @backstage/plugin-permission-backend@0.7.7-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/plugin-proxy-backend@0.6.9-next.1
+ - @backstage/plugin-scaffolder-backend@3.1.0-next.1
+ - @backstage/plugin-search-backend@2.0.9-next.1
+ - @backstage/plugin-signals-backend@0.3.11-next.1
+ - @backstage/plugin-techdocs-backend@2.1.3-next.2
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.10-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.11-next.1
+ - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1
+ - @backstage/plugin-search-backend-node@1.4.0-next.1
+
## 0.0.45-next.0
### Patch Changes
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 1d0040389c..762019d2f4 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.0.45-next.0",
+ "version": "0.0.46-next.0",
"backstage": {
"role": "backend"
},
diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md
index d40c0629f4..a8e4872744 100644
--- a/packages/cli-common/CHANGELOG.md
+++ b/packages/cli-common/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/cli-common
+## 0.1.16
+
+### Patch Changes
+
+- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way.
+- c8c2329: Add proxy configuration from env-vars to create-app tasks
+- 2bae83a: Bumped dev dependencies `@types/node`
+
+## 0.1.16-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/errors@1.2.7
+
## 0.1.16-next.1
### Patch Changes
diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json
index ea26ee09e6..0ab0a9f1fd 100644
--- a/packages/cli-common/package.json
+++ b/packages/cli-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli-common",
- "version": "0.1.16-next.1",
+ "version": "0.1.16",
"description": "Common functionality used by cli, backend, and create-app",
"backstage": {
"role": "node-library"
diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md
index 4c4005c0c9..bdacd69297 100644
--- a/packages/cli-node/CHANGELOG.md
+++ b/packages/cli-node/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/cli-node
+## 0.2.16
+
+### Patch Changes
+
+- 4e8c726: Updated to use new utilities from `@backstage/cli-common`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
## 0.2.16-next.1
### Patch Changes
diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json
index dcedc6b76c..52242bdd48 100644
--- a/packages/cli-node/package.json
+++ b/packages/cli-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli-node",
- "version": "0.2.16-next.1",
+ "version": "0.2.16",
"description": "Node.js library for Backstage CLIs",
"backstage": {
"role": "node-library"
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 9cee8632cd..28bf3b98ad 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,111 @@
# @backstage/cli
+## 0.35.2-next.0
+
+### Patch Changes
+
+- 320c6a9: Bump `@swc/core` to support `ES2023` and `ES2024`
+- 9ee5996: Bump minimum required `@swc/core` to avoid transpilation bug
+- Updated dependencies
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-common@0.1.16
+ - @backstage/cli-node@0.2.16
+ - @backstage/config@1.3.6
+ - @backstage/config-loader@1.10.7
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.0
+ - @backstage/integration@1.19.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
+## 0.35.0
+
+### Minor Changes
+
+- f6f22a9: Provide `--no-node-snapshot` by default when running the `package start` or `package test`. You can disable this behavior by providing `NODE_OPTIONS='--node-snapshot'`.
+- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`.
+
+ You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages.
+
+ You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
+
+ A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
+
+- cd0b8a1: **BREAKING**: `jest` is now a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project.
+
+ You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed.
+
+ - **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations.
+ - **Jest 30**: Install `jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27`. Fixes the stylesheet parsing warnings/errors, but requires migration steps.
+
+ See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions.
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- e7db290: Add missing peer/dev dependencies to the frontend plugin template.
+
+ `react-dom` was not declared as a peer dependency, causing module resolution
+ errors when generating plugins outside a Backstage monorepo. This adds
+ `react-dom` to `peerDependencies` (for consuming apps) and `devDependencies`
+ (for local development). `react-router-dom` is also added to `peerDependencies` (for consuming apps) and `devDependencies`
+ to support routing during plugin development.
+
+ Fixes:
+
+ - Module not found: Can't resolve 'react-dom'
+ - Module not found: Can't resolve 'react-router-dom'
+
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`.
+- 2b81751: Updated dependency `webpack` to `~5.103.0`.
+- fafd9e1: Fixed internal usage of `yargs`.
+- c8c2329: Add proxy configuration from env-vars to create-app tasks
+- 2bae83a: Switched compilation target to ES2022 in order to match the new set of supported Node.js versions, which are 22 and 24.
+
+ The TypeScript compilation target has been set to ES2022, because setting it to a higher target will break projects on older TypeScript versions. If you use a newer TypeScript version in your own project, you can bump `compilerOptions.target` to ES2023 or ES2024 in your own `tsconfig.json` file.
+
+- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-node@0.2.16
+ - @backstage/integration@1.19.0
+ - @backstage/cli-common@0.1.16
+ - @backstage/config-loader@1.10.7
+
+## 0.35.0-next.2
+
+### Minor Changes
+
+- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`.
+
+ You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages.
+
+ You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript.
+
+ A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field.
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`.
+- 2b81751: Updated dependency `webpack` to `~5.103.0`.
+- fafd9e1: Fixed internal usage of `yargs`.
+- 2bae83a: Switched ECMAScript version to ES2023.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/config-loader@1.10.7-next.1
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/cli-node@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.2.0
+ - @backstage/release-manifests@0.0.13
+ - @backstage/types@1.2.2
+
## 0.34.6-next.1
### Patch Changes
diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md
index a8df5242ad..26bd89e909 100644
--- a/packages/cli/cli-report.md
+++ b/packages/cli/cli-report.md
@@ -324,7 +324,7 @@ Options:
### `backstage-cli package test`
```
-Usage: backstage-cli [--config=] [TestPathPattern]
+Usage: backstage-cli [--config=] [TestPathPatterns]
Options:
--all
@@ -349,7 +349,6 @@ Options:
--debug
--detectLeaks
--detectOpenHandles
- --env
--errorOnDeprecated
--filter
--findRelatedTests
@@ -359,7 +358,6 @@ Options:
--globals
--haste
--ignoreProjects
- --init
--injectGlobals
--json
--lastCommit
@@ -400,13 +398,13 @@ Options:
--silent
--skipFilter
--snapshotSerializers
- --testEnvironment
+ --testEnvironment, --env
--testEnvironmentOptions
--testFailureExitCode
--testLocationInResults
--testMatch
--testPathIgnorePatterns
- --testPathPattern
+ --testPathPatterns
--testRegex
--testResultsProcessor
--testRunner
@@ -418,6 +416,7 @@ Options:
--useStderr
--verbose
--version
+ --waitForUnhandledRejections
--watch
--watchAll
--watchPathIgnorePatterns
diff --git a/packages/cli/config/getJestEnvironment.js b/packages/cli/config/getJestEnvironment.js
new file mode 100644
index 0000000000..aac2f79021
--- /dev/null
+++ b/packages/cli/config/getJestEnvironment.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function getJestMajorVersion() {
+ const jestVersion = require('jest/package.json').version;
+ const majorVersion = parseInt(jestVersion.split('.')[0], 10);
+ return majorVersion;
+}
+
+function getJestEnvironment() {
+ const majorVersion = getJestMajorVersion();
+
+ if (majorVersion >= 30) {
+ try {
+ require.resolve('@jest/environment-jsdom-abstract');
+ require.resolve('jsdom');
+ } catch {
+ throw new Error(
+ 'Jest 30+ requires @jest/environment-jsdom-abstract and jsdom. ' +
+ 'Please install them as dev dependencies.',
+ );
+ }
+ return require.resolve('./jest-environment-jsdom');
+ }
+ try {
+ require.resolve('jest-environment-jsdom');
+ } catch {
+ throw new Error(
+ 'Jest 29 requires jest-environment-jsdom. ' +
+ 'Please install it as a dev dependency.',
+ );
+ }
+ return require.resolve('jest-environment-jsdom');
+}
+
+module.exports = { getJestMajorVersion, getJestEnvironment };
diff --git a/packages/cli/config/jest-environment-jsdom/index.js b/packages/cli/config/jest-environment-jsdom/index.js
new file mode 100644
index 0000000000..9d8b76eaf5
--- /dev/null
+++ b/packages/cli/config/jest-environment-jsdom/index.js
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const JSDOMEnvironment = require('@jest/environment-jsdom-abstract').default;
+const jsdom = require('jsdom');
+
+/**
+ * A custom JSDOM environment that extends the abstract base and applies
+ * fixes for Web API globals that are missing or incorrectly implemented
+ * in JSDOM.
+ *
+ * Based on https://github.com/mswjs/jest-fixed-jsdom
+ */
+class FixedJSDOMEnvironment extends JSDOMEnvironment {
+ constructor(config, context) {
+ super(config, context, jsdom);
+
+ // Fix Web API globals that JSDOM doesn't properly expose
+ this.global.TextDecoder = TextDecoder;
+ this.global.TextEncoder = TextEncoder;
+ this.global.TextDecoderStream = TextDecoderStream;
+ this.global.TextEncoderStream = TextEncoderStream;
+ this.global.ReadableStream = ReadableStream;
+
+ this.global.Blob = Blob;
+ this.global.Headers = Headers;
+ this.global.FormData = FormData;
+ this.global.Request = Request;
+ this.global.Response = Response;
+ this.global.fetch = fetch;
+ this.global.AbortController = AbortController;
+ this.global.AbortSignal = AbortSignal;
+ this.global.structuredClone = structuredClone;
+ this.global.URL = URL;
+ this.global.URLSearchParams = URLSearchParams;
+
+ this.global.BroadcastChannel = BroadcastChannel;
+ this.global.TransformStream = TransformStream;
+ this.global.WritableStream = WritableStream;
+
+ // Needed to ensure `e instanceof Error` works as expected with errors thrown from
+ // any of the native APIs above. Without this, the JSDOM `Error` is what the test
+ // code will use for comparison with `e`, which fails the instanceof check.
+ this.global.Error = Error;
+ }
+}
+
+module.exports = FixedJSDOMEnvironment;
diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js
index f011d14b19..94e5b9c4a8 100644
--- a/packages/cli/config/jest.js
+++ b/packages/cli/config/jest.js
@@ -20,6 +20,10 @@ const crypto = require('crypto');
const glob = require('util').promisify(require('glob'));
const { version } = require('../package.json');
const paths = require('@backstage/cli-common').findPaths(process.cwd());
+const {
+ getJestEnvironment,
+ getJestMajorVersion,
+} = require('./getJestEnvironment');
const SRC_EXTS = ['ts', 'js', 'tsx', 'jsx', 'mts', 'cts', 'mjs', 'cjs'];
@@ -211,7 +215,7 @@ function getRoleConfig(role, pkgJson) {
};
if (FRONTEND_ROLES.includes(role)) {
return {
- testEnvironment: require.resolve('jest-environment-jsdom'),
+ testEnvironment: getJestEnvironment(),
// The caching module loader is only used to speed up frontend tests,
// as it breaks real dynamic imports of ESM modules.
runtime: envOptions.oldTests
@@ -279,7 +283,10 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) {
);
}
- if (options.testEnvironment === require.resolve('jest-environment-jsdom')) {
+ if (
+ options.testEnvironment === getJestEnvironment() &&
+ getJestMajorVersion() < 30 // Only needed when not running the custom env for Jest 30+
+ ) {
// FIXME https://github.com/jsdom/jsdom/issues/1724
options.setupFilesAfterEnv.unshift(require.resolve('cross-fetch/polyfill'));
}
diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js
index a3fdf106e9..5652ff67e7 100644
--- a/packages/cli/config/jestCachingModuleLoader.js
+++ b/packages/cli/config/jestCachingModuleLoader.js
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+// 'jest-runtime' is included with jest and should be kept in sync with the installed jest version
+// eslint-disable-next-line @backstage/no-undeclared-imports
const { default: JestRuntime } = require('jest-runtime');
module.exports = class CachingJestRuntime extends JestRuntime {
diff --git a/packages/cli/config/jestRejectNetworkRequests.js b/packages/cli/config/jestRejectNetworkRequests.js
index de6b486809..ef577b3a22 100644
--- a/packages/cli/config/jestRejectNetworkRequests.js
+++ b/packages/cli/config/jestRejectNetworkRequests.js
@@ -22,7 +22,7 @@ const errorMessage = 'Network requests are not allowed in tests';
const origHttpAgent = http.globalAgent;
const origHttpsAgent = https.globalAgent;
const origFetch = global.fetch;
-const origXMLHttpRequest = global.fetch;
+const origXMLHttpRequest = global.XMLHttpRequest;
http.globalAgent = new http.Agent({
lookup() {
@@ -36,10 +36,21 @@ https.globalAgent = new https.Agent({
},
});
+const BLOCKING_FETCH_SYMBOL = Symbol.for(
+ 'backstage.jestRejectNetworkRequests.blockingFetch',
+);
+
if (global.fetch) {
- global.fetch = async () => {
- throw new Error(errorMessage);
+ const blockingFetch = async (input, init) => {
+ // If global.fetch still has our marker, block the request
+ if (global.fetch[BLOCKING_FETCH_SYMBOL]) {
+ throw new Error(errorMessage);
+ }
+ // MSW (or something else) wrapped us - pass through
+ return origFetch(input, init);
};
+ blockingFetch[BLOCKING_FETCH_SYMBOL] = true;
+ global.fetch = blockingFetch;
}
if (global.XMLHttpRequest) {
diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json
index 7648e0238a..40a572da4d 100644
--- a/packages/cli/config/tsconfig.json
+++ b/packages/cli/config/tsconfig.json
@@ -33,7 +33,7 @@
"strictNullChecks": true,
"strictPropertyInitialization": true,
"stripInternal": true,
- "target": "ES2023",
+ "target": "ES2022",
"types": ["node", "jest", "webpack-env"],
"useDefineForClassFields": true
}
diff --git a/packages/cli/package.json b/packages/cli/package.json
index be94e4e817..7f340d3000 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli",
- "version": "0.34.6-next.1",
+ "version": "0.35.2-next.0",
"description": "CLI for developing Backstage plugins and apps",
"backstage": {
"role": "cli"
@@ -70,10 +70,9 @@
"@spotify/eslint-config-base": "^15.0.0",
"@spotify/eslint-config-react": "^15.0.0",
"@spotify/eslint-config-typescript": "^15.0.0",
- "@swc/core": "^1.3.46",
- "@swc/helpers": "^0.5.0",
- "@swc/jest": "^0.2.22",
- "@types/jest": "^29.5.11",
+ "@swc/core": "^1.15.6",
+ "@swc/helpers": "^0.5.17",
+ "@swc/jest": "^0.2.39",
"@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.16.0",
@@ -109,11 +108,7 @@
"handlebars": "^4.7.3",
"html-webpack-plugin": "^5.6.3",
"inquirer": "^8.2.0",
- "jest": "^29.7.0",
- "jest-cli": "^29.7.0",
"jest-css-modules": "^2.1.0",
- "jest-environment-jsdom": "^29.0.2",
- "jest-runtime": "^29.0.2",
"json-schema": "^0.4.0",
"lodash": "^4.17.21",
"minimatch": "^9.0.0",
@@ -167,6 +162,7 @@
"@backstage/plugin-scaffolder-node-test-utils": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@backstage/theme": "workspace:^",
+ "@jest/environment-jsdom-abstract": "^30.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
"@types/cross-spawn": "^6.0.2",
"@types/ejs": "^3.1.3",
@@ -174,6 +170,7 @@
"@types/fs-extra": "^11.0.0",
"@types/http-proxy": "^1.17.4",
"@types/inquirer": "^8.1.3",
+ "@types/jest": "^30.0.0",
"@types/node": "^22.13.14",
"@types/npm-packlist": "^3.0.0",
"@types/recursive-readdir": "^2.2.0",
@@ -188,6 +185,8 @@
"esbuild-loader": "^4.0.0",
"eslint-webpack-plugin": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.0",
+ "jest": "^30.2.0",
+ "jsdom": "^27.1.0",
"mini-css-extract-plugin": "^2.4.2",
"msw": "^1.0.0",
"nodemon": "^3.0.1",
@@ -196,17 +195,24 @@
"webpack-dev-server": "^5.0.0"
},
"peerDependencies": {
+ "@jest/environment-jsdom-abstract": "^30.0.0",
"@module-federation/enhanced": "^0.9.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
"esbuild-loader": "^4.0.0",
"eslint-webpack-plugin": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-environment-jsdom": "*",
+ "jsdom": "^27.1.0",
"mini-css-extract-plugin": "^2.4.2",
"terser-webpack-plugin": "^5.1.3",
"webpack": "~5.103.0",
"webpack-dev-server": "^5.0.0"
},
"peerDependenciesMeta": {
+ "@jest/environment-jsdom-abstract": {
+ "optional": true
+ },
"@module-federation/enhanced": {
"optional": true
},
@@ -222,6 +228,12 @@
"fork-ts-checker-webpack-plugin": {
"optional": true
},
+ "jest-environment-jsdom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
"mini-css-extract-plugin": {
"optional": true
},
diff --git a/packages/cli/src/modules/build/lib/runner/runBackend.test.ts b/packages/cli/src/modules/build/lib/runner/runBackend.test.ts
new file mode 100644
index 0000000000..2b97ae352c
--- /dev/null
+++ b/packages/cli/src/modules/build/lib/runner/runBackend.test.ts
@@ -0,0 +1,178 @@
+/*
+ * 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 { runBackend } from './runBackend';
+import spawn from 'cross-spawn';
+
+// Mock external dependencies
+jest.mock('chokidar', () => ({
+ watch: jest.fn(() => ({
+ on: jest.fn().mockReturnThis(),
+ add: jest.fn(),
+ })),
+}));
+
+jest.mock('cross-spawn', () =>
+ jest.fn(() => ({
+ on: jest.fn().mockReturnThis(),
+ once: jest.fn().mockReturnThis(),
+ kill: jest.fn(),
+ killed: false,
+ exitCode: null,
+ pid: 12345,
+ })),
+);
+
+jest.mock('../ipc', () => ({
+ IpcServer: jest.fn().mockImplementation(() => ({
+ addChild: jest.fn(),
+ })),
+ ServerDataStore: {
+ bind: jest.fn(),
+ },
+}));
+
+jest.mock('ctrlc-windows', () => ({
+ ctrlc: jest.fn(),
+}));
+
+describe('runBackend', () => {
+ let originalEnv: NodeJS.ProcessEnv;
+ let originalPlatform: string;
+ const mockSpawn = spawn as jest.MockedFunction;
+
+ beforeEach(() => {
+ // Use fake timers to control debounce
+ jest.useFakeTimers();
+
+ // Save original environment
+ originalEnv = { ...process.env };
+ process.env = { NODE_ENV: 'test' };
+ originalPlatform = process.platform;
+
+ // Mock process.stdin.on to prevent actual stdin reading
+ jest.spyOn(process.stdin, 'on').mockReturnValue(process.stdin);
+
+ // Mock process.once to prevent actual signal handling
+ jest.spyOn(process, 'once').mockReturnValue(process);
+ });
+
+ afterEach(() => {
+ // Restore original environment
+ process.env = originalEnv;
+ Object.defineProperty(process, 'platform', {
+ value: originalPlatform,
+ });
+
+ jest.clearAllMocks();
+ jest.useRealTimers();
+ });
+
+ describe('--no-node-snapshot argument handling', () => {
+ it('should pass --no-node-snapshot when NODE_OPTIONS is not set', () => {
+ delete process.env.NODE_OPTIONS;
+
+ runBackend({
+ entry: 'src/index',
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).toContain('--no-node-snapshot');
+ });
+
+ it('should pass --no-node-snapshot when NODE_OPTIONS exists without --node-snapshot', () => {
+ process.env.NODE_OPTIONS = '--max-old-space-size=4096';
+
+ runBackend({
+ entry: 'src/index',
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).toContain('--no-node-snapshot');
+ });
+
+ it('should not pass --no-node-snapshot when --node-snapshot already exists in NODE_OPTIONS', () => {
+ process.env.NODE_OPTIONS = '--node-snapshot --max-old-space-size=4096';
+
+ runBackend({
+ entry: 'src/index',
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).not.toContain('--no-node-snapshot');
+ });
+
+ it('should not pass --no-node-snapshot when --node-snapshot exists in the middle of NODE_OPTIONS', () => {
+ process.env.NODE_OPTIONS =
+ '--max-old-space-size=4096 --node-snapshot --inspect';
+
+ runBackend({
+ entry: 'src/index',
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).not.toContain('--no-node-snapshot');
+ });
+
+ it('should pass --no-node-snapshot even with trailing spaces in NODE_OPTIONS', () => {
+ process.env.NODE_OPTIONS = '--max-old-space-size=4096 ';
+
+ runBackend({
+ entry: 'src/index',
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).toContain('--no-node-snapshot');
+ });
+
+ it('should pass --no-node-snapshot alongside other option args like --inspect', () => {
+ delete process.env.NODE_OPTIONS;
+
+ runBackend({
+ entry: 'src/index',
+ inspectEnabled: true,
+ });
+
+ // Fast-forward past the debounce delay (100ms)
+ jest.advanceTimersByTime(100);
+
+ expect(mockSpawn).toHaveBeenCalled();
+ const spawnArgs = mockSpawn.mock.calls[0][1] as string[];
+ expect(spawnArgs).toContain('--no-node-snapshot');
+ expect(spawnArgs).toContain('--inspect');
+ });
+ });
+});
diff --git a/packages/cli/src/modules/build/lib/runner/runBackend.ts b/packages/cli/src/modules/build/lib/runner/runBackend.ts
index 49dccf804f..0550ff2563 100644
--- a/packages/cli/src/modules/build/lib/runner/runBackend.ts
+++ b/packages/cli/src/modules/build/lib/runner/runBackend.ts
@@ -47,7 +47,7 @@ export type RunBackendOptions = {
};
export async function runBackend(options: RunBackendOptions) {
- const envEnv = process.env as { NODE_ENV: string };
+ const envEnv = process.env as { NODE_ENV: string; NODE_OPTIONS?: string };
if (!envEnv.NODE_ENV) {
envEnv.NODE_ENV = 'development';
}
@@ -115,6 +115,12 @@ export async function runBackend(options: RunBackendOptions) {
}
}
+ // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder
+ // on Node LTS.
+ if (!envEnv.NODE_OPTIONS?.includes('--node-snapshot')) {
+ optionArgs.push('--no-node-snapshot');
+ }
+
const userArgs = process.argv
.slice(['node', 'backstage-cli', 'package', 'start'].length)
.filter(arg => !optionArgs.includes(arg));
diff --git a/packages/cli/src/modules/test/commands/package/test.ts b/packages/cli/src/modules/test/commands/package/test.ts
index 1ecf5b395e..992697d19c 100644
--- a/packages/cli/src/modules/test/commands/package/test.ts
+++ b/packages/cli/src/modules/test/commands/package/test.ts
@@ -78,10 +78,36 @@ export default async (_opts: OptionValues, cmd: Command) => {
process.env.TZ = 'UTC';
}
+ // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder
+ // on Node LTS.
+ if (!process.env.NODE_OPTIONS?.includes('--node-snapshot')) {
+ process.env.NODE_OPTIONS = `${
+ process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : ''
+ }--no-node-snapshot`;
+ }
+
// This ensures that the process doesn't exit too early before stdout is flushed
if (args.includes('--help')) {
(process.stdout as any)._handle.setBlocking(true);
}
+ // Because of the ongoing migration to v30 of jest, jest is no longer hard-depended to allow
+ // opt-in migration. Users instead need to add jest as a devDependency themselves and specify
+ // the version they want. This prints a helpful error message if jest is not found, i.e. they
+ // forgot/didn't know they had to add jest themselves.
+ try {
+ require.resolve('jest');
+ } catch {
+ console.error(
+ [
+ 'No Jest installation found in this project.',
+ '',
+ 'To support opt-in migration to Jest v30, the Backstage CLI now expects Jest to be installed as a devDependency.',
+ 'See the migration guide in the changelog for version options and their consequences.',
+ ].join('\n'),
+ );
+ process.exit(1);
+ }
+
await require('jest').run(args);
};
diff --git a/packages/cli/src/modules/test/commands/repo/test.ts b/packages/cli/src/modules/test/commands/repo/test.ts
index fd18ba09fe..2288d13476 100644
--- a/packages/cli/src/modules/test/commands/repo/test.ts
+++ b/packages/cli/src/modules/test/commands/repo/test.ts
@@ -17,6 +17,8 @@
import os from 'os';
import crypto from 'node:crypto';
import yargs from 'yargs';
+// 'jest-cli' is included with jest and should be kept in sync with the installed jest version
+// eslint-disable-next-line @backstage/no-undeclared-imports
import { run as runJest, yargsOptions as jestYargsOptions } from 'jest-cli';
import { relative as relativePath } from 'path';
import { Command, OptionValues } from 'commander';
@@ -289,6 +291,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise {
process.env.TZ = 'UTC';
}
+ // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder
+ // on Node LTS.
+ if (!process.env.NODE_OPTIONS?.includes('--node-snapshot')) {
+ process.env.NODE_OPTIONS = `${
+ process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : ''
+ }--no-node-snapshot`;
+ }
+
// This ensures that the process doesn't exit too early before stdout is flushed
if (args.includes('--jest-help')) {
removeOptionArg(args, '--jest-help');
diff --git a/packages/cli/templates/frontend-plugin/package.json.hbs b/packages/cli/templates/frontend-plugin/package.json.hbs
index 1ee8231228..9499d11b18 100644
--- a/packages/cli/templates/frontend-plugin/package.json.hbs
+++ b/packages/cli/templates/frontend-plugin/package.json.hbs
@@ -31,7 +31,9 @@
"react-use": "{{versionQuery 'react-use' '17.2.4'}}"
},
"peerDependencies": {
- "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
+ "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
+ "react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
+ "react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
},
"devDependencies": {
"@backstage/cli": "{{versionQuery '@backstage/cli'}}",
@@ -42,7 +44,9 @@
"@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}",
"@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}",
"msw": "{{versionQuery 'msw' '1.0.0'}}",
- "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}"
+ "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
+ "react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}",
+ "react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}"
},
"files": [
"dist"
diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md
index 13e6bfe7f6..22dd2a5df7 100644
--- a/packages/codemods/CHANGELOG.md
+++ b/packages/codemods/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/codemods
+## 0.1.53
+
+### Patch Changes
+
+- 688f070: Updated to use new utilities from `@backstage/cli-common`.
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## 0.1.53-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/cli-common@0.1.16-next.2
+
## 0.1.53-next.1
### Patch Changes
diff --git a/packages/codemods/package.json b/packages/codemods/package.json
index f78579f714..01f5ae9168 100644
--- a/packages/codemods/package.json
+++ b/packages/codemods/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/codemods",
- "version": "0.1.53-next.1",
+ "version": "0.1.53",
"description": "A collection of codemods for Backstage projects",
"backstage": {
"role": "cli"
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index 00980c43bd..84ad0e04a1 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/config-loader
+## 1.10.7
+
+### Patch Changes
+
+- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16
+
+## 1.10.7-next.1
+
+### Patch Changes
+
+- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`.
+- Updated dependencies
+ - @backstage/cli-common@0.1.16-next.2
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 1.10.7-next.0
### Patch Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 24b71ce824..e48723e5f7 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config-loader",
- "version": "1.10.7-next.0",
+ "version": "1.10.7",
"description": "Config loading functionality used by Backstage backend, and CLI",
"backstage": {
"role": "node-library"
@@ -57,7 +57,7 @@
"@backstage/cli": "workspace:^",
"@types/json-schema-merge-allof": "^0.6.0",
"@types/minimist": "^1.2.5",
- "msw": "^1.0.0",
+ "msw": "^2.0.0",
"zen-observable": "^0.10.0"
}
}
diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts
index 8908b4268b..ea226aa557 100644
--- a/packages/config-loader/src/loader.test.ts
+++ b/packages/config-loader/src/loader.test.ts
@@ -17,7 +17,7 @@
import { AppConfig } from '@backstage/config';
import { loadConfig } from './loader';
import fs from 'fs-extra';
-import { rest } from 'msw';
+import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { createDeferred } from '@backstage/types';
@@ -74,32 +74,28 @@ describe('loadConfig', () => {
});
const server = setupServer();
- const initialLoaderHandler = rest.get(
+ const initialLoaderHandler = http.get(
`https://some.domain.io/app-config.yaml`,
- (_req, res, ctx) => {
- return res(
- ctx.body(
- `app:
+ () => {
+ return new HttpResponse(
+ `app:
title: Remote Example App
sessionKey: 'abc123'
escaped: \$\${Escaped}
`,
- ),
);
},
);
- const reloadHandler = rest.get(
+ const reloadHandler = http.get(
`https://some.domain.io/app-config.yaml`,
- (_req, res, ctx) => {
- return res(
- ctx.body(
- `app:
+ () => {
+ return new HttpResponse(
+ `app:
title: NEW ReMOTe ExaMPLe App
sessionKey: 'abc123'
escaped: \$\${Escaped}
`,
- ),
);
},
);
diff --git a/packages/config-loader/src/schema/collect.test.ts b/packages/config-loader/src/schema/collect.test.ts
index d48c98fbe4..a732d982d3 100644
--- a/packages/config-loader/src/schema/collect.test.ts
+++ b/packages/config-loader/src/schema/collect.test.ts
@@ -15,14 +15,9 @@
*/
import { createMockDirectory } from '@backstage/backend-test-utils';
-import { collectConfigSchemas } from './collect';
+import { collectConfigSchemas, internal } from './collect';
import path from 'path';
-
-// cwd must be restored
-const origDir = process.cwd();
-afterAll(() => {
- process.chdir(origDir);
-});
+import { execSync } from 'child_process';
const mockSchema = {
type: 'object',
@@ -37,6 +32,36 @@ const mockSchema = {
describe('collectConfigSchemas', () => {
const mockDir = createMockDirectory();
+ // Jest 30's module resolver doesn't find nested node_modules in mock directories.
+ // Use a child process for native Node.js resolution instead.
+ const originalResolvePackagePath = internal.resolvePackagePath;
+ beforeAll(() => {
+ internal.resolvePackagePath = (name, options) => {
+ const basePath = (options && options.paths?.[0]) ?? process.cwd();
+ const baseDir = basePath.endsWith('.json')
+ ? path.dirname(basePath)
+ : basePath;
+
+ try {
+ return execSync('node', {
+ input: `console.log(require.resolve(${JSON.stringify(
+ name,
+ )}, { paths: [${JSON.stringify(baseDir)}] }))`,
+ encoding: 'utf8',
+ stdio: ['pipe', 'pipe', 'pipe'],
+ }).trim();
+ } catch {
+ const error = new Error(`Cannot find module '${name}'`);
+ (error as NodeJS.ErrnoException).code = 'MODULE_NOT_FOUND';
+ throw error;
+ }
+ };
+ });
+
+ afterAll(() => {
+ internal.resolvePackagePath = originalResolvePackagePath;
+ });
+
afterEach(() => {
mockDir.clear();
});
diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts
index 16bd654946..5f4e54387d 100644
--- a/packages/config-loader/src/schema/collect.ts
+++ b/packages/config-loader/src/schema/collect.ts
@@ -37,6 +37,17 @@ const req =
? require
: __non_webpack_require__;
+/**
+ * Exported for test mocking. Jest 30's module resolver has issues with
+ * nested node_modules, requiring tests to use an alternative resolution strategy.
+ * @internal
+ */
+export const internal = {
+ resolvePackagePath(name: string, options?: { paths: string[] }): string {
+ return req.resolve(name, options);
+ },
+};
+
/**
* This collects all known config schemas across all dependencies of the app.
*/
@@ -62,11 +73,9 @@ export async function collectConfigSchemas(
const { name, parentPath } = item;
try {
- pkgPath = req.resolve(
+ pkgPath = internal.resolvePackagePath(
`${name}/package.json`,
- parentPath && {
- paths: [parentPath],
- },
+ parentPath ? { paths: [parentPath] } : undefined,
);
} catch {
// We can somewhat safely ignore packages that don't export package.json,
diff --git a/packages/config-loader/src/sources/RemoteConfigSource.test.ts b/packages/config-loader/src/sources/RemoteConfigSource.test.ts
index 372dc91823..acb5870c7d 100644
--- a/packages/config-loader/src/sources/RemoteConfigSource.test.ts
+++ b/packages/config-loader/src/sources/RemoteConfigSource.test.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { rest } from 'msw';
+import { http, HttpResponse } from 'msw';
import { registerMswTestHooks } from '@backstage/backend-test-utils';
import { setupServer } from 'msw/node';
import { RemoteConfigSource } from './RemoteConfigSource';
@@ -26,15 +26,15 @@ describe('RemoteConfigSource', () => {
it('should load config from a remote URL', async () => {
worker.use(
- rest.get('http://localhost/config.yaml', (_req, res, ctx) =>
- res(
- ctx.body(`
+ http.get(
+ 'http://localhost/config.yaml',
+ () =>
+ new HttpResponse(`
app:
title: Example App
substituted: \${VALUE}
escaped: \$\${VALUE}
`),
- ),
),
);
@@ -61,9 +61,10 @@ app:
it('should load and parse config from a remote URL', async () => {
worker.use(
- rest.get('http://localhost/config.json', (_req, res, ctx) =>
- res(
- ctx.body(
+ http.get(
+ 'http://localhost/config.json',
+ () =>
+ new HttpResponse(
JSON.stringify({
app: {
title: 'Example App',
@@ -72,7 +73,6 @@ app:
},
}),
),
- ),
),
);
@@ -102,12 +102,12 @@ app:
let fetched = false;
worker.use(
- rest.get('http://localhost/config.yaml', (_req, res, ctx) => {
+ http.get('http://localhost/config.yaml', () => {
if (!fetched) {
fetched = true;
- return res(ctx.body('x: 1'));
+ return new HttpResponse('x: 1');
}
- return res(ctx.body('x: 2'));
+ return new HttpResponse('x: 2');
}),
);
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index cc78499c94..2b2dbeccfd 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/core-app-api
+## 1.19.3
+
+### Patch Changes
+
+- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription.
+- 97cd16f: Internal update of translation imports.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+
+## 1.19.3-next.1
+
+### Patch Changes
+
+- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription.
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+
## 1.19.3-next.0
### Patch Changes
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index d0c2d2b512..cd56c90ce8 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-app-api",
- "version": "1.19.3-next.0",
+ "version": "1.19.3",
"description": "Core app API used by Backstage apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts
index bd82d58736..2b2545f52f 100644
--- a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts
+++ b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts
@@ -60,12 +60,6 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
'https://real.com:8080/base',
'https://real.com:8080/base?c=d&e=f#g',
],
- [
- 'plugin://username:password@x?c=d&e=f#g',
- 'x',
- 'https://real.com:8080/base',
- 'https://username:password@real.com:8080/base?c=d&e=f#g',
- ],
[
'plugin://x?c=d&e=f#g',
'x',
@@ -106,14 +100,14 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
expect(inner.mock.calls[0][0]).toBe('https://elsewhere.com');
expect(inner.mock.calls[0][1].body).toBe('123');
- await outer(
- new Request('plugin://a', {
- method: 'POST',
- body: '123',
- }),
- );
+ const inputRequest = new Request('plugin://a', {
+ method: 'POST',
+ body: '123',
+ });
+
+ await outer(inputRequest);
expect(inner.mock.calls[1][0]).toBe('https://elsewhere.com');
- expect(inner.mock.calls[1][1].body).toEqual(Buffer.from('123', 'utf8'));
+ expect(inner.mock.calls[1][1]).toBe(inputRequest);
});
});
diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts
index 5993543c33..9d320c4033 100644
--- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts
+++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts
@@ -83,12 +83,9 @@ describe('AppIdentityProxy', () => {
it('should navigate to target URL on sign out', async () => {
const proxy = new AppIdentityProxy();
proxy.setTarget(mockIdentityApi, { signOutTargetUrl: '/foo' });
- Object.defineProperty(window, 'location', {
- writable: true,
- value: {},
- });
+ const navigateSpy = jest.spyOn(proxy as any, 'navigateToUrl');
await proxy.signOut();
- expect(window.location.href).toBe('/foo');
+ expect(navigateSpy).toHaveBeenCalledWith('/foo');
});
});
diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
index 941ec90d66..161092fe9f 100644
--- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
+++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
@@ -133,7 +133,11 @@ export class AppIdentityProxy implements IdentityApi {
await this.#cookieAuthSignOut?.();
- window.location.href = this.signOutTargetUrl;
+ this.navigateToUrl(this.signOutTargetUrl);
+ }
+
+ private navigateToUrl(url: string): void {
+ window.location.href = url;
}
enableCookieAuth(ctx: {
diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
index 095fc88627..4273587c00 100644
--- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
+++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
@@ -117,16 +117,12 @@ describe('ApiProvider', () => {
}).toThrow(/^API context is not available/);
}).error,
).toEqual([
- expect.objectContaining({
- detail: new Error('API context is not available'),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error('API context is not available'),
- type: 'unhandled exception',
- }),
- expect.stringMatching(
- /^The above error occurred in the component/,
+ expect.stringContaining('Error: API context is not available'),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining('Error: API context is not available'),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'The above error occurred in the component',
),
]);
@@ -137,16 +133,12 @@ describe('ApiProvider', () => {
}).toThrow(/^API context is not available/);
}).error,
).toEqual([
- expect.objectContaining({
- detail: new Error('API context is not available'),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error('API context is not available'),
- type: 'unhandled exception',
- }),
- expect.stringMatching(
- /^The above error occurred in the component/,
+ expect.stringContaining('Error: API context is not available'),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining('Error: API context is not available'),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'The above error occurred in the component',
),
]);
});
@@ -163,16 +155,16 @@ describe('ApiProvider', () => {
}).toThrow('No implementation available for apiRef{x}');
}).error,
).toEqual([
- expect.objectContaining({
- detail: new Error('No implementation available for apiRef{x}'),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error('No implementation available for apiRef{x}'),
- type: 'unhandled exception',
- }),
- expect.stringMatching(
- /^The above error occurred in the component/,
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'The above error occurred in the component',
),
]);
@@ -187,16 +179,16 @@ describe('ApiProvider', () => {
}).toThrow('No implementation available for apiRef{x}');
}).error,
).toEqual([
- expect.objectContaining({
- detail: new Error('No implementation available for apiRef{x}'),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error('No implementation available for apiRef{x}'),
- type: 'unhandled exception',
- }),
- expect.stringMatching(
- /^The above error occurred in the component/,
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(
+ 'The above error occurred in the component',
),
]);
});
diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx
index 3981da56ad..4a146678bc 100644
--- a/packages/core-app-api/src/app/AppManager.test.tsx
+++ b/packages/core-app-api/src/app/AppManager.test.tsx
@@ -670,14 +670,10 @@ describe('Integration Test', () => {
});
expect(errorLogs).toEqual([
- expect.objectContaining({
- detail: new Error(expectedMessage),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error(expectedMessage),
- type: 'unhandled exception',
- }),
+ expect.stringContaining(`Error: ${expectedMessage}`),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(`Error: ${expectedMessage}`),
+ expect.objectContaining({ type: 'unhandled-exception' }),
expect.stringContaining(
'The above error occurred in the component:',
),
@@ -714,14 +710,10 @@ describe('Integration Test', () => {
).rejects.toThrow(expectedMessage);
});
expect(errorLogs).toEqual([
- expect.objectContaining({
- detail: new Error(expectedMessage),
- type: 'unhandled exception',
- }),
- expect.objectContaining({
- detail: new Error(expectedMessage),
- type: 'unhandled exception',
- }),
+ expect.stringContaining(`Error: ${expectedMessage}`),
+ expect.objectContaining({ type: 'unhandled-exception' }),
+ expect.stringContaining(`Error: ${expectedMessage}`),
+ expect.objectContaining({ type: 'unhandled-exception' }),
expect.stringContaining(
'The above error occurred in the component:',
),
diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md
index 4f3d30deda..791b2cf1dc 100644
--- a/packages/core-compat-api/CHANGELOG.md
+++ b/packages/core-compat-api/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/core-compat-api
+## 0.5.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
## 0.5.5-next.0
### Patch Changes
diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json
index 7bb92d26fc..f8d065a611 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.5.5-next.0",
+ "version": "0.5.5",
"backstage": {
"role": "web-library"
},
diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx
index 1cb2b757e3..7094eb798c 100644
--- a/packages/core-compat-api/src/convertLegacyApp.test.tsx
+++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx
@@ -262,13 +262,16 @@ describe('convertLegacyApp', () => {
],
});
+ // Increase timeout for async rendering of complex catalog entity pages
+ const findOptions = { timeout: 5000 };
+
// Overview
const renderOverviewTest = await renderTestApp({
features: [catalogOverride, ...converted],
initialRouteEntries: ['/catalog/default/test/x'],
});
await expect(
- renderOverviewTest.findByText('overview content'),
+ renderOverviewTest.findByText('overview content', {}, findOptions),
).resolves.toBeInTheDocument();
renderOverviewTest.unmount();
@@ -277,7 +280,7 @@ describe('convertLegacyApp', () => {
initialRouteEntries: ['/catalog/default/other/x'],
});
await expect(
- renderOverviewOther.findByText('other overview content'),
+ renderOverviewOther.findByText('other overview content', {}, findOptions),
).resolves.toBeInTheDocument();
renderOverviewOther.unmount();
@@ -287,7 +290,7 @@ describe('convertLegacyApp', () => {
initialRouteEntries: ['/catalog/default/test/x/foo'],
});
await expect(
- renderFooTest.findByText('foo content'),
+ renderFooTest.findByText('foo content', {}, findOptions),
).resolves.toBeInTheDocument();
renderFooTest.unmount();
@@ -296,7 +299,7 @@ describe('convertLegacyApp', () => {
initialRouteEntries: ['/catalog/default/other/x/foo'],
});
await expect(
- renderFooOther.findByText('other foo content'),
+ renderFooOther.findByText('other foo content', {}, findOptions),
).resolves.toBeInTheDocument();
renderFooOther.unmount();
@@ -306,7 +309,7 @@ describe('convertLegacyApp', () => {
initialRouteEntries: ['/catalog/default/test/x/bar'],
});
await expect(
- renderBarTest.findByText('bar content'),
+ renderBarTest.findByText('bar content', {}, findOptions),
).resolves.toBeInTheDocument();
renderBarTest.unmount();
@@ -315,7 +318,7 @@ describe('convertLegacyApp', () => {
initialRouteEntries: ['/catalog/default/other/x/bar'],
});
await expect(
- renderBarOther.findByText('other overview content'),
+ renderBarOther.findByText('other overview content', {}, findOptions),
).resolves.toBeInTheDocument(); // /bar does not exist, fall back to rendering overview
renderBarOther.unmount();
});
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index ea2023e845..7346287cf6 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,38 @@
# @backstage/core-components
+## 0.18.4
+
+### Patch Changes
+
+- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection.
+
+ In addition, improved the `LogViewer` component by a few usability enhancements:
+
+ - Added support for multiple selections using cmd/ctrl + click
+ - Improved the generated hash that is added to the URL to also support ranges & multiple selections
+ - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality
+ - Added some color and a separator to the line numbers to improve readability
+
+- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container
+- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip
+- 5d52dab: Add i18n support for LogViewer search control
+- f6b49ce: added support for wrapLongLines option in CodeSnippet
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+
+## 0.18.4-next.2
+
+### Patch Changes
+
+- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip
+- Updated dependencies
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/version-bridge@1.0.11
+
## 0.18.4-next.1
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 2c3cee0c46..536f0ee9f0 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-components",
- "version": "0.18.4-next.1",
+ "version": "0.18.4",
"description": "Core components used by Backstage plugins and apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md
index 02fbb04805..5e842929e6 100644
--- a/packages/core-components/report.api.md
+++ b/packages/core-components/report.api.md
@@ -188,6 +188,7 @@ export interface CodeSnippetProps {
showCopyCodeButton?: boolean;
showLineNumbers?: boolean;
text: string;
+ wrapLongLines?: boolean;
}
// Warning: (ae-forgotten-export) The symbol "Props_12" needs to be exported by the entry point index.d.ts
diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
index 8c31bc4e6d..e79dc07ad3 100644
--- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
+++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx
@@ -57,6 +57,14 @@ export interface CodeSnippetProps {
* Array of line numbers to highlight
*/
highlightedNumbers?: number[];
+ /**
+ * Whether to style the `` block with `white-space: pre-wrap` or `white-space: pre`
+ *
+ * @remarks
+ *
+ * Default: false (`white-space: pre`)
+ */
+ wrapLongLines?: boolean;
/**
* Custom styles applied to code
*
@@ -79,6 +87,7 @@ export function CodeSnippet(props: CodeSnippetProps) {
language,
showLineNumbers = false,
highlightedNumbers,
+ wrapLongLines,
customStyle,
showCopyCodeButton = false,
} = props;
@@ -94,6 +103,7 @@ export function CodeSnippet(props: CodeSnippetProps) {
style={mode}
showLineNumbers={showLineNumbers}
wrapLines
+ wrapLongLines={wrapLongLines}
lineNumberStyle={{ color: theme.palette.textVerySubtle }}
lineProps={(lineNumber: number) =>
highlightedNumbers?.includes(lineNumber)
diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx
index c132d832a2..c178ba6cbf 100644
--- a/packages/core-components/src/components/Link/Link.test.tsx
+++ b/packages/core-components/src/components/Link/Link.test.tsx
@@ -50,7 +50,7 @@ describe('', () => {
External Link,
);
const externalLink = screen.getByRole('link', {
- name: 'External Link , Opens in a new window',
+ name: 'External Link, Opens in a new window',
});
const externalLinkIcon = container.querySelector('svg');
expect(externalLink).not.toContainElement(externalLinkIcon);
@@ -62,6 +62,7 @@ describe('', () => {
External Link
,
);
+ // Note: when externalLinkIcon is present, the SVG adds whitespace to the accessible name
const externalLink = screen.getByRole('link', {
name: 'External Link , Opens in a new window',
});
diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx
index 1be58d2dcf..3e4ab1df7c 100644
--- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx
+++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx
@@ -47,18 +47,20 @@ describe('TabbedLayout', () => {
});
expect(error).toEqual([
+ expect.stringContaining(
+ 'Error: Child of TabbedLayout must be an TabbedLayout.Route',
+ ),
expect.objectContaining({
- detail: new Error(
- 'Child of TabbedLayout must be an TabbedLayout.Route',
- ),
+ type: 'unhandled-exception',
}),
+ expect.stringContaining(
+ 'Error: Child of TabbedLayout must be an TabbedLayout.Route',
+ ),
expect.objectContaining({
- detail: new Error(
- 'Child of TabbedLayout must be an TabbedLayout.Route',
- ),
+ type: 'unhandled-exception',
}),
- expect.stringMatching(
- /The above error occurred in the component/,
+ expect.stringContaining(
+ 'The above error occurred in the component',
),
]);
});
diff --git a/packages/core-components/src/components/Table/Table.test.tsx b/packages/core-components/src/components/Table/Table.test.tsx
index dfae66d1c2..dab5a462c2 100644
--- a/packages/core-components/src/components/Table/Table.test.tsx
+++ b/packages/core-components/src/components/Table/Table.test.tsx
@@ -68,7 +68,7 @@ describe('
+ No scheduled tasks found for {selectedPlugin}.
+
+ }
+ detailPanel={({ rowData }) => {
+ return ;
+ }}
+ />
+ )}
+
+ );
+};
diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json
new file mode 100644
index 0000000000..89f81b740d
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json
@@ -0,0 +1,205 @@
+{
+ "tasks": [
+ {
+ "taskId": "cool-provider",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT60M"
+ },
+ "taskState": {
+ "status": "running",
+ "startedAt": "2025-10-31T21:02:36.461+00:00",
+ "timesOutAt": "2025-10-31T22:02:36.461+00:00",
+ "lastRunEndedAt": "2025-10-31T19:57:15.674+00:00",
+ "lastRunError": "{\"name\":\"error\",\"message\":\"insert into \\\"refresh_state\\\" (\\\"entity_id\\\", \\\"entity_ref\\\", \\\"errors\\\", \\\"last_discovery_at\\\", \\\"location_key\\\", \\\"next_update_at\\\", \\\"unprocessed_entity\\\", \\\"unprocessed_hash\\\") values ($1, $2, $3, CURRENT_TIMESTAMP, $4, CURRENT_TIMESTAMP, $5, $6), ($7, $8, $9, CURRENT_TIMESTAMP, $10, CURRENT_TIMESTAMP, $11, $12), ($13, $14, $15, CURRENT_TIMESTAMP, $16, CURRENT_TIMESTAMP, $17, $18), ($19, $20, $21, CURRENT_TIMESTAMP, $22, CURRENT_TIMESTAMP, $23, $24), ($25, $26, $27, CURRENT_TIMESTAMP, $28, CURRENT_TIMESTAMP, $29, $30), ($31, $32, $33, CURRENT_TIMESTAMP, $34, CURRENT_TIMESTAMP, $35, $36), ($37, $38, $39, CURRENT_TIMESTAMP, $40, CURRENT_TIMESTAMP, $41, $42), ($43, $44, $45, CURRENT_TIMESTAMP, $46, CURRENT_TIMESTAMP, $47, $48), ($49, $50, $51, CURRENT_TIMESTAMP, $52, CURRENT_TIMESTAMP, $53, $54), ($55, $56, $57, CURRENT_TIMESTAMP, $58, CURRENT_TIMESTAMP, $59, $60), ($61, $62, $63, CURRENT_TIMESTAMP, $64, CURRENT_TIMESTAMP, $65, $66), ($67, $68, $69, CURRENT_TIMESTAMP, $70, CURRENT_TIMESTAMP, $71, $72), ($73, $74, $75, CURRENT_TIMESTAMP, $76, CURRENT_TIMESTAMP, $77, $78), ($79, $80, $81, CURRENT_TIMESTAMP, $82, CURRENT_TIMESTAMP, $83, $84), ($85, $86, $87, CURRENT_TIMESTAMP, $88, CURRENT_TIMESTAMP, $89, $90), ($91, $92, $93, CURRENT_TIMESTAMP, $94, CURRENT_TIMESTAMP, $95, $96), ($97, $98, $99, CURRENT_TIMESTAMP, $100, CURRENT_TIMESTAMP, $101, $102), ($103, $104, $105, CURRENT_TIMESTAMP, $106, CURRENT_TIMESTAMP, $107, $108), ($109, $110, $111, CURRENT_TIMESTAMP, $112, CURRENT_TIMESTAMP, $113, $114), ($115, $116, $117, CURRENT_TIMESTAMP, $118, CURRENT_TIMESTAMP, $119, $120), ($121, $122, $123, CURRENT_TIMESTAMP, $124, CURRENT_TIMESTAMP, $125, $126), ($127, $128, $129, CURRENT_TIMESTAMP, $130, CURRENT_TIMESTAMP, $131, $132), ($133, $134, $135, CURRENT_TIMESTAMP, $136, CURRENT_TIMESTAMP, $137, $138), ($139, $140, $141, CURRENT_TIMESTAMP, $142, CURRENT_TIMESTAMP, $143, $144), ($145, $146, $147, CURRENT_TIMESTAMP, $148, CURRENT_TIMESTAMP, $149, $150), ($151, $152, $153, CURRENT_TIMESTAMP, $154, CURRENT_TIMESTAMP, $155, $156), ($157, $158, $159, CURRENT_TIMESTAMP, $160, CURRENT_TIMESTAMP, $161, $162), ($163, $164, $165, CURRENT_TIMESTAMP, $166, CURRENT_TIMESTAMP, $167, $168), ($169, $170, $171, CURRENT_TIMESTAMP, $172, CURRENT_TIMESTAMP, $173, $174), ($175, $176, $177, CURRENT_TIMESTAMP, $178, CURRENT_TIMESTAMP, $179, $180), ($181, $182, $183, CURRENT_TIMESTAMP, $184, CURRENT_TIMESTAMP, $185, $186), ($187, $188, $189, CURRENT_TIMESTAMP, $190, CURRENT_TIMESTAMP, $191, $192), ($193, $194, $195, CURRENT_TIMESTAMP, $196, CURRENT_TIMESTAMP, $197, $198), ($199, $200, $201, CURRENT_TIMESTAMP, $202, CURRENT_TIMESTAMP, $203, $204), ($205, $206, $207, CURRENT_TIMESTAMP, $208, CURRENT_TIMESTAMP, $209, $210), ($211, $212, $213, CURRENT_TIMESTAMP, $214, CURRENT_TIMESTAMP, $215, $216), ($217, $218, $219, CURRENT_TIMESTAMP, $220, CURRENT_TIMESTAMP, $221, $222), ($223, $224, $225, CURRENT_TIMESTAMP, $226, CURRENT_TIMESTAMP, $227, $228), ($229, $230, $231, CURRENT_TIMESTAMP, $232, CURRENT_TIMESTAMP, $233, $234), ($235, $236, $237, CURRENT_TIMESTAMP, $238, CURRENT_TIMESTAMP, $239, $240), ($241, $242, $243, CURRENT_TIMESTAMP, $244, CURRENT_TIMESTAMP, $245, $246), ($247, $248, $249, CURRENT_TIMESTAMP, $250, CURRENT_TIMESTAMP, $251, $252), ($253, $254, $255, CURRENT_TIMESTAMP, $256, CURRENT_TIMESTAMP, $257, $258), ($259, $260, $261, CURRENT_TIMESTAMP, $262, CURRENT_TIMESTAMP, $263, $264), ($265, $266, $267, CURRENT_TIMESTAMP, $268, CURRENT_TIMESTAMP, $269, $270), ($271, $272, $273, CURRENT_TIMESTAMP, $274, CURRENT_TIMESTAMP, $275, $276), ($277, $278, $279, CURRENT_TIMESTAMP, $280, CURRENT_TIMESTAMP, $281, $282), ($283, $284, $285, CURRENT_TIMESTAMP, $286, CURRENT_TIMESTAMP, $287, $288), ($289, $290, $291, CURRENT_TIMESTAMP, $292, CURRENT_TIMESTAMP, $293, $294), ($295, $296, $297, CURRENT_TIMESTAMP, $298, CURRENT_TIMESTAMP, $299, $300) - value too long for type character varying(255)\",\"length\":99,\"severity\":\"ERROR\",\"code\":\"22001\",\"file\":\"varchar.c\",\"line\":\"637\",\"routine\":\"varchar\"}"
+ },
+ "workerState": {
+ "status": "running"
+ }
+ },
+ {
+ "taskId": "some-provider",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT24H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T21:02:34.850+00:00",
+ "lastRunEndedAt": "2025-10-31T21:02:35.556+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "three-provider",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT30M",
+ "timeoutAfterDuration": "PT2M"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-10-31T21:16:04.145+00:00",
+ "lastRunEndedAt": "2025-10-31T20:46:09.184+00:00",
+ "lastRunError": "{\"name\":\"TypeError\",\"message\":\"String.prototype.replaceAll called with a non-global RegExp argument\"}"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "a-provider",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT60M",
+ "timeoutAfterDuration": "PT24H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-10-31T21:16:04.152+00:00",
+ "lastRunEndedAt": "2025-10-31T20:16:08.713+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider123:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.153+00:00",
+ "lastRunEndedAt": "2025-10-31T18:18:56.176+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider123:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.156+00:00",
+ "lastRunEndedAt": "2025-10-31T18:16:04.264+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider234:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.157+00:00",
+ "lastRunEndedAt": "2025-10-31T18:16:08.564+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider567:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.154+00:00",
+ "lastRunEndedAt": "2025-10-31T18:16:06.978+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider910:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.160+00:00",
+ "lastRunEndedAt": "2025-10-31T18:16:08.573+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "github-provider:provider000:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT24H",
+ "timeoutAfterDuration": "PT1H"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-11-01T18:16:04.161+00:00",
+ "lastRunEndedAt": "2025-10-31T18:18:54.339+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ },
+ {
+ "taskId": "catalog_orphan_cleanup",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT30S",
+ "timeoutAfterDuration": "PT24S"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-10-31T21:06:35.672+00:00",
+ "lastRunEndedAt": "2025-10-31T21:06:09.106+00:00"
+ },
+ "workerState": {
+ "status": "idle"
+ }
+ }
+ ]
+}
diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts
new file mode 100644
index 0000000000..dff8bcf653
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 { ScheduledTasksContent } from './ScheduledTasksContent';
+export { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel';
diff --git a/plugins/devtools/src/components/Content/index.ts b/plugins/devtools/src/components/Content/index.ts
index f5bd64f7c0..964e7116d9 100644
--- a/plugins/devtools/src/components/Content/index.ts
+++ b/plugins/devtools/src/components/Content/index.ts
@@ -17,3 +17,4 @@
export * from './ConfigContent';
export * from './InfoContent';
export * from './ExternalDependenciesContent';
+export * from './ScheduledTasksContent';
diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
index b2c1457161..36393db3b0 100644
--- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
+++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
@@ -20,9 +20,11 @@ import {
} from '@backstage/plugin-devtools-common';
import { ConfigContent } from '../Content';
+import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
import { DevToolsLayout } from '../DevToolsLayout';
import { InfoContent } from '../Content';
import { RequirePermission } from '@backstage/plugin-permission-react';
+import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
import { DevToolsPageProps } from '../DevToolsPage';
/** @public */
@@ -38,6 +40,11 @@ export const DefaultDevToolsPage = ({ extensions }: DevToolsPageProps) => (
+
+
+
+
+
{extensions?.map((extension, index) => (
{
+ const api = useApi(devToolsApiRef);
+
+ const {
+ value,
+ loading,
+ error: asyncError,
+ } = useAsync(async () => {
+ return api.getScheduledTasksByPlugin(plugin);
+ }, [api, plugin]);
+
+ if (asyncError) {
+ return {
+ scheduledTasks: undefined,
+ loading: false,
+ error: asyncError.message,
+ };
+ }
+
+ return {
+ scheduledTasks: value?.scheduledTasks,
+ loading,
+ error: value?.error,
+ };
+};
diff --git a/plugins/devtools/src/hooks/useTriggerScheduledTask.ts b/plugins/devtools/src/hooks/useTriggerScheduledTask.ts
new file mode 100644
index 0000000000..d26b7a8025
--- /dev/null
+++ b/plugins/devtools/src/hooks/useTriggerScheduledTask.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useState, useCallback } from 'react';
+import { devToolsApiRef } from '../api';
+import { useApi } from '@backstage/core-plugin-api';
+
+export const useTriggerScheduledTask = () => {
+ const api = useApi(devToolsApiRef);
+ const [isTriggering, setIsTriggering] = useState(false);
+ const [error, setError] = useState();
+
+ const triggerTask = useCallback(
+ async (plugin: string, taskId: string) => {
+ setIsTriggering(true);
+ setError(undefined);
+
+ try {
+ await api.triggerScheduledTask(plugin, taskId);
+ } catch (e) {
+ setError(e);
+ } finally {
+ setIsTriggering(false);
+ }
+ },
+ [api],
+ );
+
+ return {
+ triggerTask,
+ isTriggering,
+ triggerError: error?.message,
+ };
+};
diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
index d3eacfee63..23eeee8fcd 100644
--- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md
+++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-events-backend-module-aws-sqs
+## 0.4.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.4.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
## 0.4.18-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json
index 49d0ef0b3a..dbcca544dd 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.4.18-next.0",
+ "version": "0.4.18",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md
index b176789833..07db604cc3 100644
--- a/plugins/events-backend-module-azure/CHANGELOG.md
+++ b/plugins/events-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-azure
+## 0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 0.2.27-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json
index 51e79601c4..e0af5fda5c 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.27-next.0",
+ "version": "0.2.27",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
index 29a9194bd7..4acd9bf71d 100644
--- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-bitbucket-cloud
+## 0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 0.2.27-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json
index 927e8b4ca0..cca7630e0d 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.27-next.0",
+ "version": "0.2.27",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
index 1c6b8d868c..3d602aebb2 100644
--- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-bitbucket-server
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.1.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 0.1.8-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json
index b57f83a078..452b3262a0 100644
--- a/plugins/events-backend-module-bitbucket-server/package.json
+++ b/plugins/events-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-bitbucket-server",
- "version": "0.1.8-next.0",
+ "version": "0.1.8",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md
index bff39866a7..4c0750ad67 100644
--- a/plugins/events-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/events-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-gerrit
+## 0.2.27
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.2.27-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 0.2.27-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json
index 2fbe20a9eb..3d79d8805c 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.27-next.0",
+ "version": "0.2.27",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md
index 21f89ab3e2..edff58b374 100644
--- a/plugins/events-backend-module-github/CHANGELOG.md
+++ b/plugins/events-backend-module-github/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-events-backend-module-github
+## 0.4.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.4.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
## 0.4.7-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json
index 18489fe655..83513f30df 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.4.7-next.0",
+ "version": "0.4.7",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md
index f819a5918c..b97a60c140 100644
--- a/plugins/events-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/events-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-events-backend-module-gitlab
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.3.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+
## 0.3.8-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json
index ce1a568cd8..e0d509fa3b 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.3.8-next.0",
+ "version": "0.3.8",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md
index dc8d774d47..d07530ac08 100644
--- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md
+++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-events-backend-module-google-pubsub
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.1.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 0.1.7-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json
index 359c97c7f8..d5a97f3223 100644
--- a/plugins/events-backend-module-google-pubsub/package.json
+++ b/plugins/events-backend-module-google-pubsub/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-google-pubsub",
- "version": "0.1.7-next.0",
+ "version": "0.1.7",
"description": "The google-pubsub backend module for the events plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md
index 5cd64af42d..3edfcf9487 100644
--- a/plugins/events-backend-module-kafka/CHANGELOG.md
+++ b/plugins/events-backend-module-kafka/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-events-backend-module-kafka
+## 0.2.0
+
+### Minor Changes
+
+- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility.
+- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics.
+
+ This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.1.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/types@1.2.2
+
## 0.1.6-next.0
### Patch Changes
diff --git a/plugins/events-backend-module-kafka/README.md b/plugins/events-backend-module-kafka/README.md
index dbc5bf5607..f77b5d2f21 100644
--- a/plugins/events-backend-module-kafka/README.md
+++ b/plugins/events-backend-module-kafka/README.md
@@ -2,65 +2,107 @@
Welcome to the `events-backend-module-kafka` backend module!
-This package is a module for the `events-backend` backend plugin and extends the events system with an `KafkaConsumingEventPublisher.`
+This package is a module for the `events-backend` backend plugin and extends the events system with a `KafkaConsumingEventPublisher` and `KafkaPublishingEventConsumer`
-This event publisher will allow you to receive events from an Kafka queue and will publish these to the used `EventsService` implementation.
+This module provides two-way integration with Kafka:
+
+- **KafkaConsumingEventPublisher**: Receives events from Kafka queues and publishes them to the Backstage events system
+- **KafkaPublishingEventConsumer**: Consumes events from Backstage and publishes them to Kafka queues
## Configuration
-To set up Kafka queues, you need to configure the following values:
+To set up Kafka integration, you need to configure one or both of the following components:
+
+### KafkaConsumingEventPublisher Configuration
+
+To receive events from Kafka queues and publish them to Backstage:
```yaml
events:
modules:
kafka:
kafkaConsumingEventPublisher:
- clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
- brokers: # (Required) List of brokers in the Kafka cluster to connect to.
- - broker1
- - broker2
- topics:
- - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers
- kafka:
- topics: # (Required) The Kafka topics to subscribe to.
- - topic1
- groupId: your-group-id # (Required) The GroupId to be used by the topic consumers.
+ production: # Instance name, will be included in logs
+ clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ brokers: # (Required) List of brokers in the Kafka cluster to connect to.
+ - broker1
+ - broker2
+ topics:
+ - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers
+ kafka:
+ topics: # (Required) The Kafka topics to subscribe to.
+ - topic1
+ groupId: your-group-id # (Required) The GroupId to be used by the topic consumers.
+```
+
+### KafkaPublishingEventConsumer Configuration
+
+To publish events from Backstage to Kafka queues, you can configure the `KafkaPublishingEventConsumer`:
+
+```yaml
+events:
+ modules:
+ kafka:
+ kafkaPublishingEventConsumer:
+ production: # Instance name, will be included in logs
+ clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ brokers: # (Required) List of brokers in the Kafka cluster to connect to.
+ - broker1
+ - broker2
+ topics:
+ - topic: 'catalog.entity.created' # (Required) The Backstage topic to consume from
+ kafka:
+ topic: kafka-topic-name # (Required) The Kafka topic to publish to
```
For a complete list of all available fields that can be configured, refer to the [config.d.ts file](./config.d.ts).
### Optional SSL Configuration
-If your Kafka cluster requires SSL, you can configure it as follows:
+If your Kafka cluster requires SSL, you can configure it for both `kafkaConsumingEventPublisher` and `kafkaPublishingEventConsumer` instances:
```yaml
events:
modules:
kafka:
kafkaConsumingEventPublisher:
- ssl:
- rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs.
- ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format.
- key: path/to/client-key # (Optional) Private key in PEM format.
- cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format.
+ production:
+ # ... other configuration ...
+ ssl:
+ rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs.
+ ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format.
+ key: path/to/client-key # (Optional) Private key in PEM format.
+ cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format.
+ kafkaPublishingEventConsumer:
+ production:
+ # ... other configuration ...
+ ssl:
+ # Same SSL configuration options as above
```
### Optional SASL Authentication Configuration
-If your Kafka cluster requires `SASL` authentication, you can configure it as follows:
+If your Kafka cluster requires SASL authentication, you can configure it for both components:
```yaml
events:
modules:
kafka:
kafkaConsumingEventPublisher:
- sasl:
- mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512')
- username: your-username # SASL username
- password: your-password # SASL password
+ production:
+ # ... other configuration ...
+ sasl:
+ mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512')
+ username: your-username # SASL username
+ password: your-password # SASL password
+ kafkaPublishingEventConsumer:
+ production:
+ # ... other configuration ...
+ sasl:
+ # Same SASL configuration options as above
```
-This section includes optional `SSL` and `SASL` authentication configuration for enhanced security.
+These SSL and SASL configurations apply to both Kafka components and provide enhanced security for your Kafka connections.
## Installation
diff --git a/plugins/events-backend-module-kafka/config.d.ts b/plugins/events-backend-module-kafka/config.d.ts
index 49c417098c..47ebae0653 100644
--- a/plugins/events-backend-module-kafka/config.d.ts
+++ b/plugins/events-backend-module-kafka/config.d.ts
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import { HumanDuration } from '@backstage/types';
export interface Config {
@@ -25,179 +24,548 @@ export interface Config {
kafka?: {
/**
* Configuration for KafkaConsumingEventPublisher
+ *
+ * Supports either:
+ * 1. Single configuration object (legacy format)
+ * 2. Multiple named instances as a record where each key is a unique name for the Kafka instance
*/
- kafkaConsumingEventPublisher?: {
- /**
- * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
- */
- clientId: string;
- /**
- * (Required) List of brokers in the Kafka cluster to connect to.
- */
- brokers: string[];
- /**
- * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect.
- * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
- */
- ssl?:
- | {
- ca?: string[];
+ kafkaConsumingEventPublisher?:
+ | {
+ /**
+ * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ */
+ clientId: string;
+ /**
+ * (Required) List of brokers in the Kafka cluster to connect to.
+ */
+ brokers: string[];
+ /**
+ * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect.
+ * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
+ */
+ ssl?:
+ | {
+ ca?: string[];
+ /** @visibility secret */
+ key?: string;
+ cert?: string;
+ rejectUnauthorized?: boolean;
+ }
+ | boolean;
+ /**
+ * Optional SASL connection parameters.
+ */
+ sasl?: {
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512';
+ username: string;
/** @visibility secret */
- key?: string;
- cert?: string;
- rejectUnauthorized?: boolean;
- }
- | boolean;
- /**
- * Optional SASL connection parameters.
- */
- sasl?: {
- mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512';
- username: string;
- /** @visibility secret */
- password: string;
- };
+ password: string;
+ };
- /**
- * Optional retry connection parameters.
- */
- retry: {
- /**
- * (Optional) Maximum wait time for a retry
- * Default: 30000 ms.
- */
- maxRetryTime: HumanDuration | string;
-
- /**
- * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor)
- * Default: 300 ms.
- */
- initialRetryTime: HumanDuration | string;
-
- /**
- * (Optional) Randomization factor
- * Default: 0.2.
- */
- factor: number;
-
- /**
- * (Optional) Exponential factor
- * Default: 2.
- */
- multiplier: number;
-
- /**
- * (Optional) Max number of retries per call
- * Default: 5.
- */
- retries: number;
- };
-
- /**
- * (Optional) Timeout for authentication requests.
- * Default: 10000 ms.
- */
- authenticationTimeout: HumanDuration | string;
-
- /**
- * (Optional) Time to wait for a successful connection.
- * Default: 1000 ms.
- */
- connectionTimeout: HumanDuration | string;
-
- /**
- * (Optional) Time to wait for a successful request.
- * Default: 30000 ms.
- */
- requestTimeout: HumanDuration | string;
-
- /**
- * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false.
- * Default: true
- */
- enforceRequestTimeout: boolean;
-
- /**
- * Contains a object per topic for which an Kafka queue
- * should be used as source of events.
- */
- topics: Array<{
- /**
- * (Required) The Backstage topic to publish to
- */
- topic: string;
- /**
- * (Required) KafkaConsumer-related configuration.
- */
- kafka: {
/**
- * (Required) The Kafka topics to subscribe to
+ * Optional retry connection parameters.
*/
- topics: string[];
- /**
- * (Required) The GroupId to be used by the topic consumers
- */
- groupId: string;
+ retry?: {
+ /**
+ * (Optional) Maximum wait time for a retry
+ * Default: 30000 ms.
+ */
+ maxRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor)
+ * Default: 300 ms.
+ */
+ initialRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Randomization factor
+ * Default: 0.2.
+ */
+ factor?: number;
+
+ /**
+ * (Optional) Exponential factor
+ * Default: 2.
+ */
+ multiplier?: number;
+
+ /**
+ * (Optional) Max number of retries per call
+ * Default: 5.
+ */
+ retries?: number;
+ };
/**
- * (Optional) Timeout used to detect failures.
- * The consumer sends periodic heartbeats to indicate its liveness to the broker.
- * If no heartbeats are received by the broker before the expiration of this session timeout,
- * then the broker will remove this consumer from the group and initiate a rebalance
+ * (Optional) Timeout for authentication requests.
+ * Default: 10000 ms.
+ */
+ authenticationTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful connection.
+ * Default: 1000 ms.
+ */
+ connectionTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful request.
* Default: 30000 ms.
*/
- sessionTimeout: HumanDuration | string;
+ requestTimeout?: HumanDuration | string;
/**
- * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group
- * Default: 60000 ms.
+ * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false.
+ * Default: true
*/
- rebalanceTimeout: HumanDuration | string;
+ enforceRequestTimeout?: boolean;
/**
- * (Optional) The expected time between heartbeats to the consumer coordinator.
- * Heartbeats are used to ensure that the consumer's session stays active.
- * The value must be set lower than session timeout
- * Default: 3000 ms.
+ * Contains an object per topic for which a Kafka queue
+ * should be used as source of events.
*/
- heartbeatInterval: HumanDuration | string;
+ topics: Array<{
+ /**
+ * (Required) The Backstage topic to publish to
+ */
+ topic: string;
+ /**
+ * (Required) KafkaConsumer-related configuration.
+ */
+ kafka: {
+ /**
+ * (Required) The Kafka topics to subscribe to
+ */
+ topics: string[];
+ /**
+ * (Required) The GroupId to be used by the topic consumers
+ */
+ groupId: string;
- /**
- * (Optional) The period of time after which we force a refresh of metadata
- * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions
- * Default: 300000 ms (5 minutes).
- */
- metadataMaxAge: HumanDuration | string;
+ /**
+ * (Optional) Timeout used to detect failures.
+ * The consumer sends periodic heartbeats to indicate its liveness to the broker.
+ * If no heartbeats are received by the broker before the expiration of this session timeout,
+ * then the broker will remove this consumer from the group and initiate a rebalance
+ * Default: 30000 ms.
+ */
+ sessionTimeout?: HumanDuration | string;
- /**
- * (Optional) The maximum amount of data per-partition the server will return.
- * This size must be at least as large as the maximum message size the server allows
- * or else it is possible for the producer to send messages larger than the consumer can fetch.
- * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition
- * Default: 1048576 (1MB)
- */
- maxBytesPerPartition: number;
+ /**
+ * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group
+ * Default: 60000 ms.
+ */
+ rebalanceTimeout?: HumanDuration | string;
- /**
- * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate.
- * Default: 1
- */
- minBytes: number;
+ /**
+ * (Optional) The expected time between heartbeats to the consumer coordinator.
+ * Heartbeats are used to ensure that the consumer's session stays active.
+ * The value must be set lower than session timeout
+ * Default: 3000 ms.
+ */
+ heartbeatInterval?: HumanDuration | string;
- /**
- * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0
- * Default: 10485760 (10MB)
- */
- maxBytes: number;
+ /**
+ * (Optional) The period of time after which we force a refresh of metadata
+ * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions
+ * Default: 300000 ms (5 minutes).
+ */
+ metadataMaxAge?: HumanDuration | string;
- /**
- * (Optional) The maximum amount of time the server will block before answering the fetch request
- * if there isn’t sufficient data to immediately satisfy the requirement given by minBytes
- * Default: 5000
- */
- maxWaitTime: HumanDuration | string;
+ /**
+ * (Optional) The maximum amount of data per-partition the server will return.
+ * This size must be at least as large as the maximum message size the server allows
+ * or else it is possible for the producer to send messages larger than the consumer can fetch.
+ * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition
+ * Default: 1048576 (1MB)
+ */
+ maxBytesPerPartition?: number;
+
+ /**
+ * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate.
+ * Default: 1
+ */
+ minBytes?: number;
+
+ /**
+ * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0
+ * Default: 10485760 (10MB)
+ */
+ maxBytes?: number;
+
+ /**
+ * (Optional) The maximum amount of time the server will block before answering the fetch request
+ * if there isn't sufficient data to immediately satisfy the requirement given by minBytes
+ * Default: 5000
+ */
+ maxWaitTime?: HumanDuration | string;
+ };
+ }>;
+ }
+ | {
+ [name: string]: {
+ /**
+ * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ */
+ clientId: string;
+ /**
+ * (Required) List of brokers in the Kafka cluster to connect to.
+ */
+ brokers: string[];
+ /**
+ * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect.
+ * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
+ */
+ ssl?:
+ | {
+ ca?: string[];
+ /** @visibility secret */
+ key?: string;
+ cert?: string;
+ rejectUnauthorized?: boolean;
+ }
+ | boolean;
+ /**
+ * Optional SASL connection parameters.
+ */
+ sasl?: {
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512';
+ username: string;
+ /** @visibility secret */
+ password: string;
+ };
+
+ /**
+ * Optional retry connection parameters.
+ */
+ retry?: {
+ /**
+ * (Optional) Maximum wait time for a retry
+ * Default: 30000 ms.
+ */
+ maxRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor)
+ * Default: 300 ms.
+ */
+ initialRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Randomization factor
+ * Default: 0.2.
+ */
+ factor?: number;
+
+ /**
+ * (Optional) Exponential factor
+ * Default: 2.
+ */
+ multiplier?: number;
+
+ /**
+ * (Optional) Max number of retries per call
+ * Default: 5.
+ */
+ retries?: number;
+ };
+
+ /**
+ * (Optional) Timeout for authentication requests.
+ * Default: 10000 ms.
+ */
+ authenticationTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful connection.
+ * Default: 1000 ms.
+ */
+ connectionTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful request.
+ * Default: 30000 ms.
+ */
+ requestTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false.
+ * Default: true
+ */
+ enforceRequestTimeout?: boolean;
+
+ /**
+ * Contains an object per topic for which a Kafka queue
+ * should be used as source of events.
+ */
+ topics: Array<{
+ /**
+ * (Required) The Backstage topic to publish to
+ */
+ topic: string;
+ /**
+ * (Required) KafkaConsumer-related configuration.
+ */
+ kafka: {
+ /**
+ * (Required) The Kafka topics to subscribe to
+ */
+ topics: string[];
+ /**
+ * (Required) The GroupId to be used by the topic consumers
+ */
+ groupId: string;
+
+ /**
+ * (Optional) Timeout used to detect failures.
+ * The consumer sends periodic heartbeats to indicate its liveness to the broker.
+ * If no heartbeats are received by the broker before the expiration of this session timeout,
+ * then the broker will remove this consumer from the group and initiate a rebalance
+ * Default: 30000 ms.
+ */
+ sessionTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group
+ * Default: 60000 ms.
+ */
+ rebalanceTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) The expected time between heartbeats to the consumer coordinator.
+ * Heartbeats are used to ensure that the consumer's session stays active.
+ * The value must be set lower than session timeout
+ * Default: 3000 ms.
+ */
+ heartbeatInterval?: HumanDuration | string;
+
+ /**
+ * (Optional) The period of time after which we force a refresh of metadata
+ * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions
+ * Default: 300000 ms (5 minutes).
+ */
+ metadataMaxAge?: HumanDuration | string;
+
+ /**
+ * (Optional) The maximum amount of data per-partition the server will return.
+ * This size must be at least as large as the maximum message size the server allows
+ * or else it is possible for the producer to send messages larger than the consumer can fetch.
+ * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition
+ * Default: 1048576 (1MB)
+ */
+ maxBytesPerPartition?: number;
+
+ /**
+ * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate.
+ * Default: 1
+ */
+ minBytes?: number;
+
+ /**
+ * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0
+ * Default: 10485760 (10MB)
+ */
+ maxBytes?: number;
+
+ /**
+ * (Optional) The maximum amount of time the server will block before answering the fetch request
+ * if there isn't sufficient data to immediately satisfy the requirement given by minBytes
+ * Default: 5000
+ */
+ maxWaitTime?: HumanDuration | string;
+ };
+ }>;
+ };
};
- }>;
+
+ /**
+ * Configuration for KafkaPublishingEventConsumer
+ *
+ * Supports multiple named instances as a record where each key is a unique name
+ * for the Kafka producer configuration.
+ */
+ kafkaPublishingEventConsumer?: {
+ [name: string]: {
+ /**
+ * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster.
+ */
+ clientId: string;
+ /**
+ * (Required) List of brokers in the Kafka cluster to connect to.
+ */
+ brokers: string[];
+ /**
+ * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect.
+ * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options
+ */
+ ssl?:
+ | {
+ ca?: string[];
+ /** @visibility secret */
+ key?: string;
+ cert?: string;
+ rejectUnauthorized?: boolean;
+ }
+ | boolean;
+ /**
+ * Optional SASL connection parameters.
+ */
+ sasl?: {
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512';
+ username: string;
+ /** @visibility secret */
+ password: string;
+ };
+
+ /**
+ * Optional retry connection parameters.
+ */
+ retry?: {
+ /**
+ * (Optional) Maximum wait time for a retry
+ * Default: 30000 ms.
+ */
+ maxRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor)
+ * Default: 300 ms.
+ */
+ initialRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Randomization factor
+ * Default: 0.2.
+ */
+ factor?: number;
+
+ /**
+ * (Optional) Exponential factor
+ * Default: 2.
+ */
+ multiplier?: number;
+
+ /**
+ * (Optional) Max number of retries per call
+ * Default: 5.
+ */
+ retries?: number;
+ };
+
+ /**
+ * (Optional) Timeout for authentication requests.
+ * Default: 10000 ms.
+ */
+ authenticationTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful connection.
+ * Default: 1000 ms.
+ */
+ connectionTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Time to wait for a successful request.
+ * Default: 30000 ms.
+ */
+ requestTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false.
+ * Default: true
+ */
+ enforceRequestTimeout?: boolean;
+
+ /**
+ * Contains an object per topic for which a Kafka queue
+ * should be used as destination for events.
+ */
+ topics: Array<{
+ /**
+ * (Required) The Backstage topic to consume from
+ */
+ topic: string;
+ /**
+ * (Required) KafkaProducer-related configuration.
+ */
+ kafka: {
+ /**
+ * (Required) The Kafka topic to publish to
+ */
+ topic: string;
+
+ /**
+ * (Optional) Allow topic creation when querying metadata for non-existent topics.
+ * Default: true
+ */
+ allowAutoTopicCreation?: boolean;
+
+ /**
+ * (Optional) The period of time after which we force a refresh of metadata
+ * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions
+ * Default: 300000 ms (5 minutes).
+ */
+ metadataMaxAge?: HumanDuration | string;
+
+ /**
+ * (Optional) The maximum amount of time in ms that the transaction coordinator will wait for a transaction status update
+ * from the producer before proactively aborting the ongoing transaction.
+ * If this value is larger than the `transaction.max.timeout.ms`` setting in the broker, the request will fail with a `InvalidTransactionTimeout` error
+ * Default: 60000 ms.
+ */
+ transactionTimeout?: HumanDuration | string;
+
+ /**
+ * (Optional) Experimental. If enabled producer will ensure each message is written exactly once. Acks must be set to -1 ("all").
+ * Retries will default to MAX_SAFE_INTEGER.
+ * Default: false.
+ */
+ idempotent?: boolean;
+
+ /**
+ * (Optional) Max number of requests that may be in progress at any time. If falsey then no limit.
+ * Default: null.
+ */
+ maxInFlightRequests?: number;
+
+ /**
+ * Optional retry connection parameters.
+ */
+ retry?: {
+ /**
+ * (Optional) Maximum wait time for a retry
+ * Default: 30000 ms.
+ */
+ maxRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor)
+ * Default: 300 ms.
+ */
+ initialRetryTime?: HumanDuration | string;
+
+ /**
+ * (Optional) Randomization factor
+ * Default: 0.2.
+ */
+ factor?: number;
+
+ /**
+ * (Optional) Exponential factor
+ * Default: 2.
+ */
+ multiplier?: number;
+
+ /**
+ * (Optional) Max number of retries per call
+ * Default: 5.
+ */
+ retries?: number;
+ };
+ };
+ }>;
+ };
};
};
};
diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json
index a24538a617..d6e7b1ac0e 100644
--- a/plugins/events-backend-module-kafka/package.json
+++ b/plugins/events-backend-module-kafka/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-kafka",
- "version": "0.1.6-next.0",
+ "version": "0.2.0",
"description": "The kafka backend module for the events plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/events-backend-module-kafka/report.api.md b/plugins/events-backend-module-kafka/report.api.md
index a6b129b82e..c5e6d29c6a 100644
--- a/plugins/events-backend-module-kafka/report.api.md
+++ b/plugins/events-backend-module-kafka/report.api.md
@@ -6,6 +6,6 @@
import { BackendFeature } from '@backstage/backend-plugin-api';
// @public
-const eventsModuleKafkaConsumingEventPublisher: BackendFeature;
-export default eventsModuleKafkaConsumingEventPublisher;
+const _default: BackendFeature;
+export default _default;
```
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts
new file mode 100644
index 0000000000..98781936b0
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
+import { Kafka } from 'kafkajs';
+import { ConfigReader } from '@backstage/config';
+import { mockServices } from '@backstage/backend-test-utils';
+
+jest.mock('kafkajs');
+
+describe('KafkaConsumingEventPublisher', () => {
+ const mockLogger = mockServices.logger.mock();
+ const mockEvents = mockServices.events.mock();
+
+ const mockConsumer = {
+ connect: jest.fn(),
+ disconnect: jest.fn(),
+ subscribe: jest.fn(),
+ run: jest.fn(),
+ };
+
+ const mockKafkaClient = {
+ consumer: jest.fn().mockReturnValue(mockConsumer),
+ } as unknown as Kafka;
+
+ jest.mocked(Kafka).mockImplementation(() => mockKafkaClient);
+
+ const mockConfig = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: [
+ {
+ topic: 'backstage-topic',
+ kafka: {
+ topics: ['test-topic'],
+ groupId: 'test-group',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create instances from config', () => {
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toBeInstanceOf(Array);
+ expect(consumers).toHaveLength(1);
+ expect(consumers[0]).toBeInstanceOf(KafkaConsumingEventPublisher);
+ });
+
+ it('should return empty array when no config', () => {
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
+ config: new ConfigReader({}),
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toEqual([]);
+ });
+
+ it('should start all consumers', async () => {
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toHaveLength(1);
+
+ await consumers[0].start();
+
+ expect(mockConsumer.connect).toHaveBeenCalled();
+ expect(mockConsumer.subscribe).toHaveBeenCalledWith({
+ topics: ['test-topic'],
+ });
+ expect(mockConsumer.run).toHaveBeenCalled();
+ });
+
+ it('should shutdown all consumers', async () => {
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toHaveLength(1);
+
+ await consumers[0].shutdown();
+
+ expect(mockConsumer.disconnect).toHaveBeenCalled();
+ });
+
+ it('should handle multiple consumer configs', () => {
+ const multiConsumerConfig = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092'],
+ topics: [
+ {
+ topic: 'topic1',
+ kafka: {
+ topics: ['kafka-topic-1'],
+ groupId: 'group1',
+ },
+ },
+ {
+ topic: 'topic2',
+ kafka: {
+ topics: ['kafka-topic-2'],
+ groupId: 'group2',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
+ config: multiConsumerConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toHaveLength(1);
+ expect(mockKafkaClient.consumer).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts
new file mode 100644
index 0000000000..b996032575
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { LoggerService } from '@backstage/backend-plugin-api';
+import { EventsService } from '@backstage/plugin-events-node';
+import { Consumer, Kafka } from 'kafkajs';
+import {
+ KafkaConsumerConfig,
+ KafkaConsumingEventPublisherConfig,
+ readConsumerConfig,
+} from './config';
+import { Config } from '@backstage/config';
+import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter';
+import { convertHeadersToMetadata } from '../utils/kafkaTransformers';
+
+type KafkaConsumer = {
+ consumer: Consumer;
+ config: KafkaConsumerConfig;
+};
+
+/**
+ * This class subscribes to Kafka topics and publishes events received to the registered subscriber.
+ * The message payload will be used as the event payload and passed to the subscribers.
+ */
+export class KafkaConsumingEventPublisher {
+ private readonly kafkaConsumers: KafkaConsumer[];
+ private readonly logger: LoggerService;
+
+ static fromConfig(env: {
+ config: Config;
+ events: EventsService;
+ logger: LoggerService;
+ }): KafkaConsumingEventPublisher[] {
+ const configs = readConsumerConfig(env.config, env.logger);
+
+ return configs.map(
+ kafkaConfig =>
+ new KafkaConsumingEventPublisher(env.logger, env.events, kafkaConfig),
+ );
+ }
+
+ private constructor(
+ logger: LoggerService,
+ private readonly events: EventsService,
+ config: KafkaConsumingEventPublisherConfig,
+ ) {
+ this.logger = logger.child({
+ class: KafkaConsumingEventPublisher.prototype.constructor.name,
+ instance: config.instance,
+ });
+
+ const kafka = new Kafka({
+ ...config.kafkaConfig,
+ logCreator: loggerServiceAdapter(this.logger),
+ });
+
+ this.kafkaConsumers = config.kafkaConsumerConfigs.map(consumerConfig => ({
+ consumer: kafka.consumer(consumerConfig.consumerConfig),
+ config: consumerConfig,
+ }));
+ }
+
+ async start(): Promise {
+ await Promise.all(
+ this.kafkaConsumers.map(async ({ consumer, config }) => {
+ const consumerLogger = this.logger.child({
+ id: `events.kafka.publisher:${config.backstageTopic}`,
+ groupId: config.consumerConfig.groupId,
+ kafkaTopics: config.consumerSubscribeTopics.topics.toString(),
+ backstageTopic: config.backstageTopic,
+ });
+ try {
+ await consumer.connect();
+ await consumer.subscribe(config.consumerSubscribeTopics);
+
+ await consumer.run({
+ eachMessage: async ({ message }) => {
+ this.events.publish({
+ topic: config.backstageTopic,
+ eventPayload: JSON.parse(message.value?.toString()!),
+ metadata: convertHeadersToMetadata(message.headers),
+ });
+ },
+ });
+ } catch (error: any) {
+ consumerLogger.error('Kafka consumer connection failed', error);
+ }
+ }),
+ );
+ }
+
+ async shutdown(): Promise {
+ await Promise.all(
+ this.kafkaConsumers.map(({ consumer }) => consumer.disconnect()),
+ );
+ }
+}
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts
new file mode 100644
index 0000000000..16ee9e0a4c
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 { readConsumerConfig } from './config';
+import { mockServices } from '@backstage/backend-test-utils';
+
+const mockLogger = mockServices.logger.mock();
+
+describe('readConsumerConfig', () => {
+ it('not configured', () => {
+ const publisherConfigs = readConsumerConfig(
+ new ConfigReader({}),
+ mockLogger,
+ );
+
+ expect(publisherConfigs).toEqual([]);
+ });
+
+ it('only required fields configured', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topics: ['topic-A'],
+ groupId: 'my-group',
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topics: ['topic-B'],
+ groupId: 'my-group',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readConsumerConfig(config, mockLogger);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+ expect(devConfig.kafkaConsumerConfigs.length).toBe(2);
+
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+
+ expect(devConfig.kafkaConsumerConfigs).toEqual([
+ {
+ backstageTopic: 'fake1',
+ consumerConfig: {
+ groupId: 'my-group',
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-A'],
+ },
+ },
+ {
+ backstageTopic: 'fake2',
+ consumerConfig: {
+ groupId: 'my-group',
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-B'],
+ },
+ },
+ ]);
+ });
+
+ it('all fields configured', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ ssl: true,
+ sasl: {
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ },
+ retry: {
+ maxRetryTime: { milliseconds: 20000 },
+ initialRetryTime: { milliseconds: 200 },
+ factor: '0.4',
+ multiplier: '4',
+ retries: '10',
+ },
+ authenticationTimeout: { milliseconds: 20000 },
+ connectionTimeout: { milliseconds: 1500 },
+ requestTimeout: { milliseconds: 20000 },
+ enforceRequestTimeout: false,
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topics: ['topic-A'],
+ groupId: 'my-group',
+ sessionTimeout: { milliseconds: 20000 },
+ rebalanceTimeout: { milliseconds: 50000 },
+ heartbeatInterval: { milliseconds: 2000 },
+ metadataMaxAge: { milliseconds: 400000 },
+ maxBytesPerPartition: 50000,
+ minBytes: 2,
+ maxBytes: 500000,
+ maxWaitTime: { milliseconds: 4000 },
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topics: ['topic-B'],
+ groupId: 'my-group',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readConsumerConfig(config, mockLogger);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+
+ // Client configuration
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+ expect(devConfig.kafkaConfig.ssl).toBeTruthy();
+ expect(devConfig.kafkaConfig.sasl).toStrictEqual({
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ });
+ expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000);
+ expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500);
+ expect(devConfig.kafkaConfig.requestTimeout).toBe(20000);
+ expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy();
+ expect(devConfig.kafkaConfig.retry).toStrictEqual({
+ maxRetryTime: 20000,
+ initialRetryTime: 200,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+
+ // Consumer configuration
+ expect(devConfig.kafkaConsumerConfigs.length).toBe(2);
+
+ expect(devConfig.kafkaConsumerConfigs).toEqual([
+ {
+ backstageTopic: 'fake1',
+ consumerConfig: {
+ groupId: 'my-group',
+ sessionTimeout: 20000,
+ rebalanceTimeout: 50000,
+ heartbeatInterval: 2000,
+ metadataMaxAge: 400000,
+ maxBytesPerPartition: 50000,
+ minBytes: 2,
+ maxBytes: 500000,
+ maxWaitTimeInMs: 4000,
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-A'],
+ },
+ },
+ {
+ backstageTopic: 'fake2',
+ consumerConfig: {
+ groupId: 'my-group',
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-B'],
+ },
+ },
+ ]);
+ });
+
+ it('should handle HumanDuration and string values for durations and timeouts', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ retry: {
+ maxRetryTime: { seconds: 1 },
+ initialRetryTime: { minutes: 1 },
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ },
+ authenticationTimeout: { hours: 1 },
+ connectionTimeout: { days: 1 },
+ topics: [],
+ requestTimeout: '1m',
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readConsumerConfig(config, mockLogger);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+
+ // Client configuration
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+ expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000);
+ expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000);
+ expect(devConfig.kafkaConfig.requestTimeout).toBe(60000);
+ expect(devConfig.kafkaConfig.retry).toStrictEqual({
+ maxRetryTime: 1000,
+ initialRetryTime: 60000,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+
+ // Consumer configuration
+ expect(devConfig.kafkaConsumerConfigs.length).toBe(0);
+ });
+
+ it('single instance configuration (legacy format)', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topics: ['topic-A'],
+ groupId: 'my-group',
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topics: ['topic-B'],
+ groupId: 'my-group',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readConsumerConfig(config, mockLogger);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const defaultConfig = publisherConfigs[0];
+ expect(defaultConfig.instance).toBe('default');
+ expect(defaultConfig.kafkaConsumerConfigs.length).toBe(2);
+
+ expect(defaultConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(defaultConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+
+ expect(defaultConfig.kafkaConsumerConfigs).toEqual([
+ {
+ backstageTopic: 'fake1',
+ consumerConfig: {
+ groupId: 'my-group',
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-A'],
+ },
+ },
+ {
+ backstageTopic: 'fake2',
+ consumerConfig: {
+ groupId: 'my-group',
+ },
+ consumerSubscribeTopics: {
+ topics: ['topic-B'],
+ },
+ },
+ ]);
+
+ // Verify deprecation warning was logged
+ expect(mockLogger.warn).toHaveBeenCalledWith(
+ 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.',
+ );
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts
new file mode 100644
index 0000000000..b07378a1d6
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs';
+import {
+ readKafkaConfig,
+ readOptionalHumanDurationInMs,
+} from '../utils/config';
+import { LoggerService } from '@backstage/backend-plugin-api';
+
+export interface KafkaConsumerConfig {
+ backstageTopic: string;
+ consumerConfig: ConsumerConfig;
+ consumerSubscribeTopics: ConsumerSubscribeTopics;
+}
+
+export interface KafkaConsumingEventPublisherConfig {
+ instance: string;
+ kafkaConfig: KafkaConfig;
+ kafkaConsumerConfigs: KafkaConsumerConfig[];
+}
+
+const CONFIG_PREFIX_PUBLISHER =
+ 'events.modules.kafka.kafkaConsumingEventPublisher';
+
+const processSinglePublisher = (
+ instanceName: string,
+ publisherConfig: Config,
+): KafkaConsumingEventPublisherConfig => {
+ return {
+ instance: instanceName,
+ kafkaConfig: readKafkaConfig(publisherConfig),
+ kafkaConsumerConfigs: publisherConfig
+ .getConfigArray('topics')
+ .map(topicConfig => {
+ return {
+ backstageTopic: topicConfig.getString('topic'),
+ consumerConfig: {
+ groupId: topicConfig.getString('kafka.groupId'),
+ sessionTimeout: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.sessionTimeout',
+ ),
+ rebalanceTimeout: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.rebalanceTimeout',
+ ),
+ heartbeatInterval: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.heartbeatInterval',
+ ),
+ metadataMaxAge: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.metadataMaxAge',
+ ),
+ maxBytesPerPartition: topicConfig.getOptionalNumber(
+ 'kafka.maxBytesPerPartition',
+ ),
+ minBytes: topicConfig.getOptionalNumber('kafka.minBytes'),
+ maxBytes: topicConfig.getOptionalNumber('kafka.maxBytes'),
+ maxWaitTimeInMs: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.maxWaitTime',
+ ),
+ },
+ consumerSubscribeTopics: {
+ topics: topicConfig.getStringArray('kafka.topics'),
+ },
+ };
+ }),
+ };
+};
+
+export const readConsumerConfig = (
+ config: Config,
+ logger: LoggerService,
+): KafkaConsumingEventPublisherConfig[] => {
+ const publishersConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER);
+
+ // Check for legacy single publisher format
+ if (publishersConfig?.getOptionalString('clientId')) {
+ logger.warn(
+ 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.',
+ );
+ return [
+ processSinglePublisher(
+ 'default', // use `default` as instance name for legacy single config
+ publishersConfig,
+ ),
+ ];
+ }
+
+ return (
+ publishersConfig
+ ?.keys()
+ ?.map(publisherKey =>
+ processSinglePublisher(
+ publisherKey,
+ publishersConfig.getConfig(publisherKey),
+ ),
+ ) ?? []
+ );
+};
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts
new file mode 100644
index 0000000000..94d36a81e0
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 { eventsModuleKafkaConsumingEventPublisher } from './module';
diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts
new file mode 100644
index 0000000000..19ac969800
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 } from '@backstage/backend-plugin-api';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
+import { eventsServiceRef } from '@backstage/plugin-events-node';
+import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
+import { eventsModuleKafkaConsumingEventPublisher } from './module';
+import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
+
+jest.mock('./KafkaConsumingEventPublisher');
+
+describe('eventsModuleKafkaConsumingEventPublisher', () => {
+ it('should be correctly wired and set up', async () => {
+ const events = new TestEventsService();
+ const eventsServiceFactory = createServiceFactory({
+ service: eventsServiceRef,
+ deps: {},
+ async factory({}) {
+ return events;
+ },
+ });
+
+ const mockKafkaConsumingEventPublisher = {
+ start: jest.fn(),
+ shutdown: jest.fn(),
+ } as unknown as KafkaConsumingEventPublisher;
+
+ jest
+ .mocked(KafkaConsumingEventPublisher.fromConfig)
+ .mockReturnValue([mockKafkaConsumingEventPublisher]);
+
+ await startTestBackend({
+ features: [
+ eventsServiceFactory,
+ eventsModuleKafkaConsumingEventPublisher,
+ mockServices.rootConfig.factory({
+ data: {
+ events: {
+ modules: {
+ kafka: {
+ kafkaConsumingEventPublisher: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: {
+ fake1: {
+ kafka: {
+ topics: ['topic-A'],
+ groupId: 'my-group',
+ },
+ },
+ fake2: {
+ kafka: {
+ topics: ['topic-B'],
+ groupId: 'my-group',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }),
+ ],
+ });
+
+ // Verify that the Kafka consumer client was started
+ expect(mockKafkaConsumingEventPublisher.start).toHaveBeenCalled();
+
+ // Verify that the shutdown hook was registered
+ expect(mockKafkaConsumingEventPublisher.shutdown).not.toHaveBeenCalled();
+ });
+
+ it('should handle empty configuration gracefully', async () => {
+ const events = new TestEventsService();
+ const eventsServiceFactory = createServiceFactory({
+ service: eventsServiceRef,
+ deps: {},
+ async factory({}) {
+ return events;
+ },
+ });
+
+ jest.mocked(KafkaConsumingEventPublisher.fromConfig).mockReturnValue([]);
+
+ await startTestBackend({
+ features: [
+ eventsServiceFactory,
+ eventsModuleKafkaConsumingEventPublisher,
+ mockServices.rootConfig.factory({
+ data: {
+ events: {
+ modules: {
+ kafka: {
+ // No kafkaConsumingEventPublisher config
+ },
+ },
+ },
+ },
+ }),
+ ],
+ });
+
+ // Verify that fromConfig was called but returned empty array
+ expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalled();
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts
similarity index 69%
rename from plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts
rename to plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts
index b524454ca0..8ad3396c76 100644
--- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts
+++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts
@@ -17,11 +17,11 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
-import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient';
import { eventsServiceRef } from '@backstage/plugin-events-node';
+import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
/**
- * Kafka module for the Events plugin.
+ * Reads messages off of Kafka topics and forwards them into the Backstage events system.
*
* @public
*/
@@ -34,22 +34,22 @@ export const eventsModuleKafkaConsumingEventPublisher = createBackendModule({
config: coreServices.rootConfig,
events: eventsServiceRef,
logger: coreServices.logger,
- lifecycle: coreServices.lifecycle,
+ lifecycle: coreServices.rootLifecycle,
},
async init({ config, logger, events, lifecycle }) {
- const kafka = KafkaConsumerClient.fromConfig({
+ const consumers = KafkaConsumingEventPublisher.fromConfig({
config,
events,
logger,
});
- if (!kafka) {
- return;
- }
+ lifecycle.addStartupHook(async () => {
+ await Promise.all(consumers.map(consumer => consumer.start()));
+ });
- await kafka.start();
-
- lifecycle.addShutdownHook(async () => await kafka.shutdown());
+ lifecycle.addShutdownHook(async () => {
+ await Promise.all(consumers.map(consumer => consumer.shutdown()));
+ });
},
});
},
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts
new file mode 100644
index 0000000000..6379605901
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer';
+import { Kafka } from 'kafkajs';
+import { mockServices } from '@backstage/backend-test-utils';
+import { ConfigReader } from '@backstage/config';
+
+jest.mock('kafkajs');
+
+describe('KafkaPublishingEventConsumer', () => {
+ const mockLogger = mockServices.logger.mock();
+ const mockEvents = mockServices.events.mock();
+
+ const mockProducer = {
+ connect: jest.fn(),
+ disconnect: jest.fn(),
+ send: jest.fn(),
+ };
+
+ const mockKafkaClient = {
+ producer: jest.fn().mockReturnValue(mockProducer),
+ } as unknown as Kafka;
+
+ jest.mocked(Kafka).mockImplementation(() => mockKafkaClient);
+
+ const mockConfig = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092'],
+ topics: [
+ {
+ topic: 'backstage-topic',
+ kafka: {
+ topic: 'kafka-topic',
+ allowAutoTopicCreation: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create instances from config', () => {
+ const consumers = KafkaPublishingEventConsumer.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ expect(consumers).toHaveLength(1);
+ expect(consumers[0]).toBeInstanceOf(KafkaPublishingEventConsumer);
+ });
+
+ it('should start the consumer and subscribe to events', async () => {
+ const consumers = KafkaPublishingEventConsumer.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ await consumers[0].start();
+
+ expect(mockProducer.connect).toHaveBeenCalled();
+ expect(mockEvents.subscribe).toHaveBeenCalledWith({
+ id: 'kafka:publisher:backstage-topic',
+ topics: ['backstage-topic'],
+ onEvent: expect.any(Function),
+ });
+ });
+
+ it('should shutdown the producer', async () => {
+ const consumers = KafkaPublishingEventConsumer.fromConfig({
+ config: mockConfig,
+ events: mockEvents,
+ logger: mockLogger,
+ });
+
+ await consumers[0].shutdown();
+
+ expect(mockProducer.disconnect).toHaveBeenCalled();
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts
new file mode 100644
index 0000000000..f9443f2d69
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { LoggerService } from '@backstage/backend-plugin-api';
+import { EventParams, EventsService } from '@backstage/plugin-events-node';
+import { Kafka, Producer } from 'kafkajs';
+import {
+ KafkaPublisherConfig,
+ KafkaPublishingEventConsumerConfig,
+ readPublisherConfig,
+} from './config';
+import { Config } from '@backstage/config';
+import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter';
+import { payloadToBuffer } from '../utils/kafkaTransformers';
+
+type KafkaPublisher = {
+ producer: Producer;
+ config: KafkaPublisherConfig;
+};
+
+/**
+ * This class subscribes to Backstage internal events and publishes them to Kafka topics.
+ * The internal event payload will be serialized and sent to the configured Kafka topic.
+ */
+export class KafkaPublishingEventConsumer {
+ private readonly kafkaPublishers: KafkaPublisher[];
+ private readonly logger: LoggerService;
+
+ static fromConfig(env: {
+ config: Config;
+ events: EventsService;
+ logger: LoggerService;
+ }): KafkaPublishingEventConsumer[] {
+ const configs = readPublisherConfig(env.config);
+
+ return configs.map(
+ kafkaConfig =>
+ new KafkaPublishingEventConsumer(env.logger, env.events, kafkaConfig),
+ );
+ }
+
+ private constructor(
+ logger: LoggerService,
+ private readonly events: EventsService,
+ config: KafkaPublishingEventConsumerConfig,
+ ) {
+ this.logger = logger.child({
+ class: KafkaPublishingEventConsumer.prototype.constructor.name,
+ instance: config.instance,
+ });
+
+ const kafka = new Kafka({
+ ...config.kafkaConfig,
+ logCreator: loggerServiceAdapter(this.logger),
+ });
+
+ this.kafkaPublishers = config.kafkaPublisherConfigs.map(
+ publisherConfig => ({
+ producer: kafka.producer(publisherConfig.producerConfig),
+ config: publisherConfig,
+ }),
+ );
+ }
+
+ async start(): Promise {
+ await Promise.all(
+ this.kafkaPublishers.map(async ({ producer, config }) => {
+ try {
+ await producer.connect();
+
+ this.events.subscribe({
+ id: `kafka:publisher:${config.backstageTopic}`,
+ topics: [config.backstageTopic],
+ onEvent: async (params: EventParams) => {
+ await producer.send({
+ topic: config.kafkaTopic,
+ messages: [
+ {
+ value: payloadToBuffer(params.eventPayload),
+ },
+ ],
+ });
+ },
+ });
+ this.logger.info(
+ `Subscribed to EventService, publishing events to external topic: ${config.backstageTopic}`,
+ );
+ } catch (error: any) {
+ this.logger.error(
+ `Kafka producer connection failed for topic ${config.backstageTopic}`,
+ error,
+ );
+ }
+ }),
+ );
+ }
+
+ async shutdown(): Promise {
+ await Promise.all(
+ this.kafkaPublishers.map(({ producer }) => producer.disconnect()),
+ );
+ }
+}
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts
new file mode 100644
index 0000000000..bd4d622238
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 { readPublisherConfig } from './config';
+
+describe('readPublisherConfig', () => {
+ it('not configured', () => {
+ const publisherConfigs = readPublisherConfig(new ConfigReader({}));
+
+ expect(publisherConfigs).toEqual([]);
+ });
+
+ it('only required fields configured', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topic: 'topic-A',
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topic: 'topic-B',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readPublisherConfig(config);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+ expect(devConfig.kafkaPublisherConfigs.length).toBe(2);
+
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+
+ expect(devConfig.kafkaPublisherConfigs).toEqual([
+ {
+ backstageTopic: 'fake1',
+ kafkaTopic: 'topic-A',
+ producerConfig: {
+ allowAutoTopicCreation: undefined,
+ metadataMaxAge: undefined,
+ transactionTimeout: undefined,
+ idempotent: undefined,
+ maxInFlightRequests: undefined,
+ retry: {},
+ },
+ },
+ {
+ backstageTopic: 'fake2',
+ kafkaTopic: 'topic-B',
+ producerConfig: {
+ allowAutoTopicCreation: undefined,
+ metadataMaxAge: undefined,
+ transactionTimeout: undefined,
+ idempotent: undefined,
+ maxInFlightRequests: undefined,
+ retry: {},
+ },
+ },
+ ]);
+ });
+
+ it('all fields configured', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ ssl: true,
+ sasl: {
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ },
+ retry: {
+ maxRetryTime: { milliseconds: 20000 },
+ initialRetryTime: { milliseconds: 200 },
+ factor: '0.4',
+ multiplier: '4',
+ retries: '10',
+ },
+ authenticationTimeout: { milliseconds: 20000 },
+ connectionTimeout: { milliseconds: 1500 },
+ requestTimeout: { milliseconds: 20000 },
+ enforceRequestTimeout: false,
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topic: 'topic-A',
+ allowAutoTopicCreation: true,
+ metadataMaxAge: { milliseconds: 400000 },
+ transactionTimeout: { milliseconds: 30000 },
+ idempotent: true,
+ maxInFlightRequests: 5,
+ retry: {
+ maxRetryTime: { milliseconds: 15000 },
+ initialRetryTime: { milliseconds: 100 },
+ factor: '0.2',
+ multiplier: '2',
+ retries: '5',
+ },
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topic: 'topic-B',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readPublisherConfig(config);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+
+ // Client configuration
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+ expect(devConfig.kafkaConfig.ssl).toBeTruthy();
+ expect(devConfig.kafkaConfig.sasl).toStrictEqual({
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ });
+ expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000);
+ expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500);
+ expect(devConfig.kafkaConfig.requestTimeout).toBe(20000);
+ expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy();
+ expect(devConfig.kafkaConfig.retry).toStrictEqual({
+ maxRetryTime: 20000,
+ initialRetryTime: 200,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+
+ // Publisher configuration
+ expect(devConfig.kafkaPublisherConfigs.length).toBe(2);
+
+ expect(devConfig.kafkaPublisherConfigs).toEqual([
+ {
+ backstageTopic: 'fake1',
+ kafkaTopic: 'topic-A',
+ producerConfig: {
+ allowAutoTopicCreation: true,
+ metadataMaxAge: 400000,
+ transactionTimeout: 30000,
+ idempotent: true,
+ maxInFlightRequests: 5,
+ retry: {
+ maxRetryTime: 15000,
+ initialRetryTime: 100,
+ factor: 0.2,
+ multiplier: 2,
+ retries: 5,
+ },
+ },
+ },
+ {
+ backstageTopic: 'fake2',
+ kafkaTopic: 'topic-B',
+ producerConfig: {
+ allowAutoTopicCreation: undefined,
+ metadataMaxAge: undefined,
+ transactionTimeout: undefined,
+ idempotent: undefined,
+ maxInFlightRequests: undefined,
+ retry: {},
+ },
+ },
+ ]);
+ });
+
+ it('should handle HumanDuration and string values for durations and timeouts', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ retry: {
+ maxRetryTime: { seconds: 1 },
+ initialRetryTime: { minutes: 1 },
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ },
+ authenticationTimeout: { hours: 1 },
+ connectionTimeout: { days: 1 },
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topic: 'topic-A',
+ metadataMaxAge: { seconds: 300 },
+ transactionTimeout: '30s',
+ },
+ },
+ ],
+ requestTimeout: '1m',
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readPublisherConfig(config);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(1);
+
+ const devConfig = publisherConfigs[0];
+ expect(devConfig.instance).toBe('dev');
+
+ // Client configuration
+ expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events');
+ expect(devConfig.kafkaConfig.brokers).toEqual([
+ 'kafka1:9092',
+ 'kafka2:9092',
+ ]);
+ expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000);
+ expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000);
+ expect(devConfig.kafkaConfig.requestTimeout).toBe(60000);
+ expect(devConfig.kafkaConfig.retry).toStrictEqual({
+ maxRetryTime: 1000,
+ initialRetryTime: 60000,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+
+ // Publisher configuration
+ expect(devConfig.kafkaPublisherConfigs.length).toBe(1);
+ expect(
+ devConfig.kafkaPublisherConfigs[0].producerConfig.metadataMaxAge,
+ ).toBe(300000);
+ expect(
+ devConfig.kafkaPublisherConfigs[0].producerConfig.transactionTimeout,
+ ).toBe(30000);
+ });
+
+ it('should handle multiple instances', () => {
+ const config = new ConfigReader({
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-dev',
+ brokers: ['kafka-dev:9092'],
+ topics: [
+ {
+ topic: 'dev-topic',
+ kafka: {
+ topic: 'kafka-dev-topic',
+ },
+ },
+ ],
+ },
+ prod: {
+ clientId: 'backstage-prod',
+ brokers: ['kafka-prod1:9092', 'kafka-prod2:9092'],
+ topics: [
+ {
+ topic: 'prod-topic',
+ kafka: {
+ topic: 'kafka-prod-topic',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ });
+
+ const publisherConfigs = readPublisherConfig(config);
+
+ expect(publisherConfigs).toBeDefined();
+ expect(Array.isArray(publisherConfigs)).toBe(true);
+ expect(publisherConfigs).toHaveLength(2);
+
+ const devConfig = publisherConfigs.find(c => c.instance === 'dev')!;
+ expect(devConfig.kafkaConfig.clientId).toBe('backstage-dev');
+ expect(devConfig.kafkaConfig.brokers).toEqual(['kafka-dev:9092']);
+ expect(devConfig.kafkaPublisherConfigs).toHaveLength(1);
+ expect(devConfig.kafkaPublisherConfigs[0].backstageTopic).toBe('dev-topic');
+
+ const prodConfig = publisherConfigs.find(c => c.instance === 'prod')!;
+ expect(prodConfig.kafkaConfig.clientId).toBe('backstage-prod');
+ expect(prodConfig.kafkaConfig.brokers).toEqual([
+ 'kafka-prod1:9092',
+ 'kafka-prod2:9092',
+ ]);
+ expect(prodConfig.kafkaPublisherConfigs).toHaveLength(1);
+ expect(prodConfig.kafkaPublisherConfigs[0].backstageTopic).toBe(
+ 'prod-topic',
+ );
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts
new file mode 100644
index 0000000000..8439e24405
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 {
+ readKafkaConfig,
+ readOptionalHumanDurationInMs,
+ readRetryConfig,
+} from '../utils/config';
+import { KafkaConfig, ProducerConfig } from 'kafkajs';
+
+export interface KafkaPublisherConfig {
+ backstageTopic: string;
+ kafkaTopic: string;
+ producerConfig: ProducerConfig;
+}
+
+export interface KafkaPublishingEventConsumerConfig {
+ instance: string;
+ kafkaConfig: KafkaConfig;
+ kafkaPublisherConfigs: KafkaPublisherConfig[];
+}
+
+const CONFIG_PREFIX_PUBLISHER =
+ 'events.modules.kafka.kafkaPublishingEventConsumer';
+
+export const readPublisherConfig = (
+ config: Config,
+): KafkaPublishingEventConsumerConfig[] => {
+ const publishers = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER);
+
+ return (
+ publishers?.keys()?.map(publisherKey => {
+ const publisherConfig = publishers.getConfig(publisherKey);
+
+ return {
+ instance: publisherKey,
+ kafkaConfig: readKafkaConfig(publisherConfig),
+ kafkaPublisherConfigs: publisherConfig
+ .getConfigArray('topics')
+ .map(topicConfig => {
+ return {
+ backstageTopic: topicConfig.getString('topic'),
+ kafkaTopic: topicConfig.getString('kafka.topic'),
+ producerConfig: {
+ allowAutoTopicCreation: topicConfig.getOptionalBoolean(
+ 'kafka.allowAutoTopicCreation',
+ ),
+ metadataMaxAge: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.metadataMaxAge',
+ ),
+ transactionTimeout: readOptionalHumanDurationInMs(
+ topicConfig,
+ 'kafka.transactionTimeout',
+ ),
+ idempotent: topicConfig.getOptionalBoolean('kafka.idempotent'),
+ maxInFlightRequests: topicConfig.getOptionalNumber(
+ 'kafka.maxInFlightRequests',
+ ),
+ retry: readRetryConfig(
+ topicConfig.getOptionalConfig('kafka.retry'),
+ ),
+ },
+ };
+ }),
+ };
+ }) ?? []
+ );
+};
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts
new file mode 100644
index 0000000000..42fadd2a5a
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 { eventsModuleKafkaPublishingEventConsumer } from './module';
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts
new file mode 100644
index 0000000000..39c8268300
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 } from '@backstage/backend-plugin-api';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
+import { eventsServiceRef } from '@backstage/plugin-events-node';
+import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
+import { eventsModuleKafkaPublishingEventConsumer } from './module';
+import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer';
+
+jest.mock('./KafkaPublishingEventConsumer');
+
+describe('eventsModuleKafkaPublishingEventConsumer', () => {
+ it('should be correctly wired and set up', async () => {
+ const events = new TestEventsService();
+ const eventsServiceFactory = createServiceFactory({
+ service: eventsServiceRef,
+ deps: {},
+ async factory({}) {
+ return events;
+ },
+ });
+
+ const mockKafkaPublishingEventConsumer = {
+ start: jest.fn(),
+ shutdown: jest.fn(),
+ } as unknown as KafkaPublishingEventConsumer;
+
+ jest
+ .mocked(KafkaPublishingEventConsumer.fromConfig)
+ .mockReturnValue([mockKafkaPublishingEventConsumer]);
+
+ await startTestBackend({
+ features: [
+ eventsServiceFactory,
+ eventsModuleKafkaPublishingEventConsumer,
+ mockServices.rootConfig.factory({
+ data: {
+ events: {
+ modules: {
+ kafka: {
+ kafkaPublishingEventConsumer: {
+ dev: {
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ topics: [
+ {
+ topic: 'fake1',
+ kafka: {
+ topic: 'topic-A',
+ },
+ },
+ {
+ topic: 'fake2',
+ kafka: {
+ topic: 'topic-B',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ }),
+ ],
+ });
+
+ // Verify that the Kafka publishing event consumer was started
+ expect(mockKafkaPublishingEventConsumer.start).toHaveBeenCalled();
+
+ // Verify that the shutdown hook was registered (but not called yet)
+ expect(mockKafkaPublishingEventConsumer.shutdown).not.toHaveBeenCalled();
+ });
+
+ it('should handle empty configuration gracefully', async () => {
+ const events = new TestEventsService();
+ const eventsServiceFactory = createServiceFactory({
+ service: eventsServiceRef,
+ deps: {},
+ async factory({}) {
+ return events;
+ },
+ });
+
+ jest.mocked(KafkaPublishingEventConsumer.fromConfig).mockReturnValue([]);
+
+ await startTestBackend({
+ features: [
+ eventsServiceFactory,
+ eventsModuleKafkaPublishingEventConsumer,
+ mockServices.rootConfig.factory({
+ data: {
+ events: {
+ modules: {
+ kafka: {
+ // No kafkaPublishingEventConsumer config
+ },
+ },
+ },
+ },
+ }),
+ ],
+ });
+
+ // Verify that fromConfig was called but returned empty array
+ expect(KafkaPublishingEventConsumer.fromConfig).toHaveBeenCalled();
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts
new file mode 100644
index 0000000000..dd5f0c50ab
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ coreServices,
+ createBackendModule,
+} from '@backstage/backend-plugin-api';
+import { eventsServiceRef } from '@backstage/plugin-events-node';
+import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer';
+
+/**
+ * Reads internal Backstage events and forwards them to Kafka topics.
+ *
+ * @public
+ */
+export const eventsModuleKafkaPublishingEventConsumer = createBackendModule({
+ pluginId: 'events',
+ moduleId: 'kafka-publishing-event-consumer',
+ register(env) {
+ env.registerInit({
+ deps: {
+ config: coreServices.rootConfig,
+ events: eventsServiceRef,
+ logger: coreServices.logger,
+ lifecycle: coreServices.rootLifecycle,
+ },
+ async init({ config, logger, events, lifecycle }) {
+ const consumers = KafkaPublishingEventConsumer.fromConfig({
+ config,
+ events,
+ logger,
+ });
+
+ lifecycle.addStartupHook(async () => {
+ await Promise.all(consumers.map(consumer => consumer.start()));
+ });
+
+ lifecycle.addShutdownHook(async () => {
+ await Promise.all(consumers.map(consumer => consumer.shutdown()));
+ });
+ },
+ });
+ },
+});
diff --git a/plugins/events-backend-module-kafka/src/index.ts b/plugins/events-backend-module-kafka/src/index.ts
index 3ba6207d7d..af4fb02bf1 100644
--- a/plugins/events-backend-module-kafka/src/index.ts
+++ b/plugins/events-backend-module-kafka/src/index.ts
@@ -13,14 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { createBackendFeatureLoader } from '@backstage/backend-plugin-api';
+import { eventsModuleKafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
+import { eventsModuleKafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer';
/**
* The module "kafka" for the Backstage backend plugin "events"
- * adding an Kafka-based publisher,
- * receiving events from an Kafka topic and passing it to the
- * internal event broker.
+ * adding Kafka-based event handling:
+ * - Consumer: receives events from Kafka topics and passes them to the internal event broker
+ * - Publisher: receives internal events and publishes them to Kafka topics
*
* @packageDocumentation
*/
-export { eventsModuleKafkaConsumingEventPublisher as default } from './service/eventsModuleKafkaConsumingEventPublisher';
+export default createBackendFeatureLoader({
+ *loader() {
+ yield eventsModuleKafkaConsumingEventPublisher;
+ yield eventsModuleKafkaPublishingEventConsumer;
+ },
+});
diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts
deleted file mode 100644
index 5824bf4d41..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { KafkaConsumerClient } from './KafkaConsumerClient';
-import { ConfigReader } from '@backstage/config';
-import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
-import { mockServices } from '@backstage/backend-test-utils';
-
-jest.mock('kafkajs');
-jest.mock('./KafkaConsumingEventPublisher');
-
-describe('KafkaConsumerClient', () => {
- const mockLogger = mockServices.logger.mock();
- const mockEvents = mockServices.events.mock();
-
- const mockConfig = new ConfigReader({
- events: {
- modules: {
- kafka: {
- kafkaConsumingEventPublisher: {
- clientId: 'backstage-events',
- brokers: ['kafka1:9092', 'kafka2:9092'],
- topics: [
- {
- topic: 'fake1',
- kafka: {
- topics: ['topic-A'],
- groupId: 'my-group',
- },
- },
- {
- topic: 'fake2',
- kafka: {
- topics: ['topic-B'],
- groupId: 'my-group',
- },
- },
- ],
- },
- },
- },
- },
- });
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('should create an instance from config', () => {
- const client = KafkaConsumerClient.fromConfig({
- config: mockConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(client).toBeInstanceOf(KafkaConsumerClient);
- });
-
- it('should not create an instance from config', () => {
- const client = KafkaConsumerClient.fromConfig({
- config: new ConfigReader({}),
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(client).toBeUndefined();
- });
-
- it('should create a consumer for each topic from config', () => {
- KafkaConsumerClient.fromConfig({
- config: mockConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalledTimes(2);
- });
-
- it('should start all consumers', async () => {
- const mockConsumer = {
- start: jest.fn().mockResolvedValue(undefined),
- };
- (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue(
- mockConsumer,
- );
-
- const client = KafkaConsumerClient.fromConfig({
- config: mockConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(client).toBeDefined();
-
- await client?.start();
-
- expect(mockConsumer.start).toHaveBeenCalled();
- });
-
- it('should shutdown all consumers', async () => {
- const mockConsumer = {
- shutdown: jest.fn().mockResolvedValue(undefined),
- };
- (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue(
- mockConsumer,
- );
-
- const client = KafkaConsumerClient.fromConfig({
- config: mockConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(client).toBeDefined();
-
- await client?.shutdown();
-
- expect(mockConsumer.shutdown).toHaveBeenCalled();
- });
-});
diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts
deleted file mode 100644
index d506e4a6af..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { LoggerService } from '@backstage/backend-plugin-api';
-import { Config } from '@backstage/config';
-import { EventsService } from '@backstage/plugin-events-node';
-import { Kafka } from 'kafkajs';
-import { KafkaEventSourceConfig, readConfig } from './config';
-import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
-import { loggerServiceAdapter } from './LoggerServiceAdapter';
-
-/**
- * KafkaConsumerClient
- *
- * This class creates the Kafka client that will be used to create the KafkaConsumingEventPublisher
- */
-export class KafkaConsumerClient {
- private readonly kafka: Kafka;
- private readonly consumers: KafkaConsumingEventPublisher[];
-
- static fromConfig(options: {
- config: Config;
- events: EventsService;
- logger: LoggerService;
- }): KafkaConsumerClient | undefined {
- const kafkaConfig = readConfig(options.config);
-
- if (!kafkaConfig) {
- options.logger.info(
- 'Kafka consumer not configured, skipping initialization',
- );
- return undefined;
- }
-
- return new KafkaConsumerClient(options.logger, options.events, kafkaConfig);
- }
-
- private constructor(
- logger: LoggerService,
- events: EventsService,
- config: KafkaEventSourceConfig,
- ) {
- this.kafka = new Kafka({
- ...config.kafkaConfig,
- logCreator: loggerServiceAdapter(logger),
- });
-
- this.consumers = config.kafkaConsumerConfigs.map(consumerConfig =>
- KafkaConsumingEventPublisher.fromConfig({
- kafkaClient: this.kafka,
- config: consumerConfig,
- logger,
- events,
- }),
- );
- }
-
- async start(): Promise {
- this.consumers.map(async consumer => await consumer.start());
- }
-
- async shutdown(): Promise {
- this.consumers.map(async consumer => await consumer.shutdown());
- }
-}
diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts
deleted file mode 100644
index 64c53595ad..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher';
-import { Kafka } from 'kafkajs';
-import { KafkaConsumerConfig } from './config';
-import { mockServices } from '@backstage/backend-test-utils';
-
-jest.mock('kafkajs');
-
-describe('KafkaConsumingEventPublisher', () => {
- const mockLogger = mockServices.logger.mock();
- const mockEvents = mockServices.events.mock();
-
- const mockConsumer = {
- connect: jest.fn(),
- disconnect: jest.fn(),
- subscribe: jest.fn(),
- run: jest.fn(),
- };
-
- const mockKafkaClient = {
- consumer: jest.fn().mockReturnValue(mockConsumer),
- } as unknown as Kafka;
-
- const kafkaConsumerConfig: KafkaConsumerConfig = {
- consumerConfig: {
- groupId: 'test-group',
- },
- consumerSubscribeTopics: {
- topics: ['test-topic'],
- },
- backstageTopic: 'backstage-topic',
- };
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('should create an instance from config', () => {
- const consumer = KafkaConsumingEventPublisher.fromConfig({
- kafkaClient: mockKafkaClient,
- config: kafkaConsumerConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- expect(consumer).toBeInstanceOf(KafkaConsumingEventPublisher);
- });
-
- it('should start the consumer', async () => {
- const consumer = KafkaConsumingEventPublisher.fromConfig({
- kafkaClient: mockKafkaClient,
- config: kafkaConsumerConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- await consumer.start();
-
- expect(mockConsumer.connect).toHaveBeenCalled();
- expect(mockConsumer.subscribe).toHaveBeenCalledWith(
- kafkaConsumerConfig.consumerSubscribeTopics,
- );
- expect(mockConsumer.run).toHaveBeenCalled();
- });
-
- it('should shutdown the consumer', async () => {
- const consumer = KafkaConsumingEventPublisher.fromConfig({
- kafkaClient: mockKafkaClient,
- config: kafkaConsumerConfig,
- events: mockEvents,
- logger: mockLogger,
- });
-
- await consumer.shutdown();
-
- expect(mockConsumer.disconnect).toHaveBeenCalled();
- });
-});
diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts
deleted file mode 100644
index dfc6186fb5..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { LoggerService } from '@backstage/backend-plugin-api';
-import { EventParams, EventsService } from '@backstage/plugin-events-node';
-import { Consumer, ConsumerSubscribeTopics, IHeaders, Kafka } from 'kafkajs';
-import { KafkaConsumerConfig } from './config';
-
-type EventMetadata = EventParams['metadata'];
-
-/**
- * This class subscribes to Kafka topics and publishes events received to the registered subscriber.
- * The message payload will be used as the event payload and passed to the subscribers.
- */
-export class KafkaConsumingEventPublisher {
- private readonly kafkaConsumer: Consumer;
- private readonly consumerSubscribeTopics: ConsumerSubscribeTopics;
- private readonly backstageTopic: string;
- private readonly logger: LoggerService;
-
- static fromConfig(env: {
- kafkaClient: Kafka;
- config: KafkaConsumerConfig;
- events: EventsService;
- logger: LoggerService;
- }): KafkaConsumingEventPublisher {
- return new KafkaConsumingEventPublisher(
- env.kafkaClient,
- env.logger,
- env.events,
- env.config,
- );
- }
-
- private readonly events: EventsService;
-
- private constructor(
- kafkaClient: Kafka,
- logger: LoggerService,
- events: EventsService,
- config: KafkaConsumerConfig,
- ) {
- this.events = events;
- this.kafkaConsumer = kafkaClient.consumer(config.consumerConfig);
- this.consumerSubscribeTopics = config.consumerSubscribeTopics;
- this.backstageTopic = config.backstageTopic;
- const id = `events.kafka.publisher:${this.backstageTopic}`;
- this.logger = logger.child({
- class: KafkaConsumingEventPublisher.prototype.constructor.name,
- groupId: config.consumerConfig.groupId,
- kafkaTopics: config.consumerSubscribeTopics.topics.toString(),
- backstageTopic: config.backstageTopic,
- taskId: id,
- });
- }
-
- async start(): Promise {
- try {
- await this.kafkaConsumer.connect();
-
- await this.kafkaConsumer.subscribe(this.consumerSubscribeTopics);
-
- await this.kafkaConsumer.run({
- eachMessage: async ({ message }) => {
- this.events.publish({
- topic: this.backstageTopic,
- eventPayload: JSON.parse(message.value?.toString()!),
- metadata: this.convertHeadersToMetadata(message.headers),
- });
- },
- });
- } catch (error: any) {
- this.logger.error('Kafka consumer connection failed ', error);
- }
- }
-
- async shutdown(): Promise {
- await this.kafkaConsumer.disconnect();
- }
-
- private convertHeadersToMetadata = (
- headers: IHeaders | undefined,
- ): EventParams['metadata'] => {
- if (!headers) return undefined;
-
- const metadata: EventMetadata = {};
-
- Object.entries(headers).forEach(([key, value]) => {
- // If value is an array use toString() on all values converting any Buffer types to valid strings
- if (Array.isArray(value)) metadata[key] = value.map(v => v.toString());
- // Always return the values using toString() to catch all Buffer types that should be converted to strings
- else metadata[key] = value?.toString();
- });
-
- return metadata;
- };
-}
diff --git a/plugins/events-backend-module-kafka/src/publisher/config.test.ts b/plugins/events-backend-module-kafka/src/publisher/config.test.ts
deleted file mode 100644
index fc7e3049b0..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/config.test.ts
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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 { readConfig } from './config';
-
-describe('readConfig', () => {
- it('not configured', () => {
- const publisherConfigs = readConfig(new ConfigReader({}));
-
- expect(publisherConfigs).toBeUndefined();
- });
-
- it('only required fields configured', () => {
- const config = new ConfigReader({
- events: {
- modules: {
- kafka: {
- kafkaConsumingEventPublisher: {
- clientId: 'backstage-events',
- brokers: ['kafka1:9092', 'kafka2:9092'],
- topics: [
- {
- topic: 'fake1',
- kafka: {
- topics: ['topic-A'],
- groupId: 'my-group',
- },
- },
- {
- topic: 'fake2',
- kafka: {
- topics: ['topic-B'],
- groupId: 'my-group',
- },
- },
- ],
- },
- },
- },
- },
- });
-
- const publisherConfigs = readConfig(config);
-
- expect(publisherConfigs).toBeDefined();
-
- expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2);
-
- expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events');
- expect(publisherConfigs?.kafkaConfig.brokers).toEqual([
- 'kafka1:9092',
- 'kafka2:9092',
- ]);
- expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual(
- 'fake1',
- );
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId,
- ).toEqual('my-group');
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics,
- ).toEqual(['topic-A']);
- });
-
- it('all fields configured', () => {
- const config = new ConfigReader({
- events: {
- modules: {
- kafka: {
- kafkaConsumingEventPublisher: {
- clientId: 'backstage-events',
- brokers: ['kafka1:9092', 'kafka2:9092'],
- ssl: true,
- sasl: {
- mechanism: 'plain',
- username: 'username',
- password: 'password',
- },
- retry: {
- maxRetryTime: { milliseconds: 20000 },
- initialRetryTime: { milliseconds: 200 },
- factor: '0.4',
- multiplier: '4',
- retries: '10',
- },
- authenticationTimeout: { milliseconds: 20000 },
- connectionTimeout: { milliseconds: 1500 },
- requestTimeout: { milliseconds: 20000 },
- enforceRequestTimeout: false,
- topics: [
- {
- topic: 'fake1',
- kafka: {
- topics: ['topic-A'],
- groupId: 'my-group',
- sessionTimeout: { milliseconds: 20000 },
- rebalanceTimeout: { milliseconds: 50000 },
- heartbeatInterval: { milliseconds: 2000 },
- metadataMaxAge: { milliseconds: 400000 },
- maxBytesPerPartition: 50000,
- minBytes: 2,
- maxBytes: 500000,
- maxWaitTime: { milliseconds: 4000 },
- },
- },
- {
- topic: 'fake2',
- kafka: {
- topics: ['topic-B'],
- groupId: 'my-group',
- },
- },
- ],
- },
- },
- },
- },
- });
-
- const publisherConfigs = readConfig(config);
-
- expect(publisherConfigs).toBeDefined();
-
- // Client configuration
- expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events');
- expect(publisherConfigs?.kafkaConfig.brokers).toEqual([
- 'kafka1:9092',
- 'kafka2:9092',
- ]);
- expect(publisherConfigs?.kafkaConfig.ssl).toBeTruthy();
- expect(publisherConfigs?.kafkaConfig.sasl).toStrictEqual({
- mechanism: 'plain',
- username: 'username',
- password: 'password',
- });
- expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(20000);
- expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(1500);
- expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(20000);
- expect(publisherConfigs?.kafkaConfig.enforceRequestTimeout).toBeFalsy();
- expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({
- maxRetryTime: 20000,
- initialRetryTime: 200,
- factor: 0.4,
- multiplier: 4,
- retries: 10,
- });
-
- // Consumer configuration
- expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2);
- expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual(
- 'fake1',
- );
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId,
- ).toEqual('my-group');
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics,
- ).toEqual(['topic-A']);
-
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.sessionTimeout,
- ).toBe(20000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.rebalanceTimeout,
- ).toBe(50000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig
- .heartbeatInterval,
- ).toBe(2000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.metadataMaxAge,
- ).toBe(400000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig
- .maxBytesPerPartition,
- ).toBe(50000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.minBytes,
- ).toBe(2);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxBytes,
- ).toBe(500000);
- expect(
- publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxWaitTimeInMs,
- ).toBe(4000);
- });
-
- it('should handle HumanDuration and string values for durations and timeouts', () => {
- const config = new ConfigReader({
- events: {
- modules: {
- kafka: {
- kafkaConsumingEventPublisher: {
- clientId: 'backstage-events',
- brokers: ['kafka1:9092', 'kafka2:9092'],
- retry: {
- maxRetryTime: { seconds: 1 },
- initialRetryTime: { minutes: 1 },
- factor: 0.4,
- multiplier: 4,
- retries: 10,
- },
- authenticationTimeout: { hours: 1 },
- connectionTimeout: { days: 1 },
- topics: [],
- requestTimeout: '1m',
- },
- },
- },
- },
- });
-
- const publisherConfigs = readConfig(config);
-
- expect(publisherConfigs).toBeDefined();
-
- // Client configuration
- expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events');
- expect(publisherConfigs?.kafkaConfig.brokers).toEqual([
- 'kafka1:9092',
- 'kafka2:9092',
- ]);
- expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(3600000);
- expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(86400000);
- expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(60000);
- expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({
- maxRetryTime: 1000,
- initialRetryTime: 60000,
- factor: 0.4,
- multiplier: 4,
- retries: 10,
- });
-
- // Consumer configuration
- expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(0);
- });
-});
diff --git a/plugins/events-backend-module-kafka/src/publisher/config.ts b/plugins/events-backend-module-kafka/src/publisher/config.ts
deleted file mode 100644
index 7534ce4d38..0000000000
--- a/plugins/events-backend-module-kafka/src/publisher/config.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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 { durationToMilliseconds } from '@backstage/types';
-import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs';
-
-export interface KafkaConsumerConfig {
- backstageTopic: string;
- consumerConfig: ConsumerConfig;
- consumerSubscribeTopics: ConsumerSubscribeTopics;
-}
-
-export interface KafkaEventSourceConfig {
- kafkaConfig: KafkaConfig;
- kafkaConsumerConfigs: KafkaConsumerConfig[];
-}
-
-const CONFIG_PREFIX_PUBLISHER =
- 'events.modules.kafka.kafkaConsumingEventPublisher';
-
-/**
- * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined.
- *
- * @param config - The configuration object to read from.
- * @param key - The key to look up in the configuration.
- * @returns The duration in milliseconds, or undefined if the key is not defined.
- */
-const readOptionalHumanDurationInMs = (
- config: Config,
- key: string,
-): number | undefined => {
- const humanDuration = config.has(key)
- ? readDurationFromConfig(config, { key })
- : undefined;
-
- if (!humanDuration) return undefined;
-
- return durationToMilliseconds(humanDuration);
-};
-
-export const readConfig = (
- config: Config,
-): KafkaEventSourceConfig | undefined => {
- const kafkaConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER);
-
- if (!kafkaConfig) {
- return undefined;
- }
-
- const clientId = kafkaConfig.getString('clientId');
- const brokers = kafkaConfig.getStringArray('brokers');
-
- const authenticationTimeout = readOptionalHumanDurationInMs(
- kafkaConfig,
- 'authenticationTimeout',
- );
-
- const connectionTimeout = readOptionalHumanDurationInMs(
- kafkaConfig,
- 'connectionTimeout',
- );
- const requestTimeout = readOptionalHumanDurationInMs(
- kafkaConfig,
- 'requestTimeout',
- );
- const enforceRequestTimeout = kafkaConfig.getOptionalBoolean(
- 'enforceRequestTimeout',
- );
-
- const ssl = kafkaConfig.getOptional('ssl') as KafkaConfig['ssl'];
- const sasl = kafkaConfig.getOptional('sasl') as KafkaConfig['sasl'];
-
- const retry: KafkaConfig['retry'] = {
- maxRetryTime: readOptionalHumanDurationInMs(
- kafkaConfig,
- 'retry.maxRetryTime',
- ),
- initialRetryTime: readOptionalHumanDurationInMs(
- kafkaConfig,
- 'retry.initialRetryTime',
- ),
- factor: kafkaConfig.getOptionalNumber('retry.factor'),
- multiplier: kafkaConfig.getOptionalNumber('retry.multiplier'),
- retries: kafkaConfig.getOptionalNumber('retry.retries'),
- };
-
- const kafkaConsumerConfigs: KafkaConsumerConfig[] = kafkaConfig
- .getConfigArray('topics')
- .map(topic => {
- return {
- backstageTopic: topic.getString('topic'),
- consumerConfig: {
- groupId: topic.getString('kafka.groupId'),
- sessionTimeout: readOptionalHumanDurationInMs(
- topic,
- 'kafka.sessionTimeout',
- ),
- rebalanceTimeout: readOptionalHumanDurationInMs(
- topic,
- 'kafka.rebalanceTimeout',
- ),
- heartbeatInterval: readOptionalHumanDurationInMs(
- topic,
- 'kafka.heartbeatInterval',
- ),
- metadataMaxAge: readOptionalHumanDurationInMs(
- topic,
- 'kafka.metadataMaxAge',
- ),
- maxBytesPerPartition: topic.getOptionalNumber(
- 'kafka.maxBytesPerPartition',
- ),
- minBytes: topic.getOptionalNumber('kafka.minBytes'),
- maxBytes: topic.getOptionalNumber('kafka.maxBytes'),
- maxWaitTimeInMs: readOptionalHumanDurationInMs(
- topic,
- 'kafka.maxWaitTime',
- ),
- },
- consumerSubscribeTopics: {
- topics: topic.getStringArray('kafka.topics'),
- },
- };
- });
-
- return {
- kafkaConfig: {
- clientId,
- brokers,
- ssl,
- sasl,
- authenticationTimeout,
- connectionTimeout,
- requestTimeout,
- enforceRequestTimeout,
- retry,
- },
- kafkaConsumerConfigs,
- };
-};
diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts
deleted file mode 100644
index 816793f635..0000000000
--- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2025 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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 } from '@backstage/backend-plugin-api';
-import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
-import { eventsServiceRef } from '@backstage/plugin-events-node';
-import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
-import { eventsModuleKafkaConsumingEventPublisher } from './eventsModuleKafkaConsumingEventPublisher';
-import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient';
-
-jest.mock('../publisher/KafkaConsumerClient');
-
-describe('eventsModuleKafkaConsumingEventPublisher', () => {
- it('should be correctly wired and set up', async () => {
- const events = new TestEventsService();
- const eventsServiceFactory = createServiceFactory({
- service: eventsServiceRef,
- deps: {},
- async factory({}) {
- return events;
- },
- });
-
- const mockKafkaConsumerClient = {
- start: jest.fn(),
- shutdown: jest.fn(),
- };
- (KafkaConsumerClient.fromConfig as jest.Mock).mockReturnValue(
- mockKafkaConsumerClient,
- );
-
- await startTestBackend({
- features: [
- eventsServiceFactory,
- eventsModuleKafkaConsumingEventPublisher,
- mockServices.rootConfig.factory({
- data: {
- events: {
- modules: {
- kafka: {
- kafkaConsumingEventPublisher: {
- clientId: 'backstage-events',
- brokers: ['kafka1:9092', 'kafka2:9092'],
- topics: {
- fake1: {
- kafka: {
- topics: ['topic-A'],
- groupId: 'my-group',
- },
- },
- fake2: {
- kafka: {
- topics: ['topic-B'],
- groupId: 'my-group',
- },
- },
- },
- },
- },
- },
- },
- },
- }),
- ],
- });
-
- // Verify that the Kafka consumer client was started
- expect(mockKafkaConsumerClient.start).toHaveBeenCalled();
-
- // Verify that the shutdown hook was registered
- expect(mockKafkaConsumerClient.shutdown).not.toHaveBeenCalled();
- });
-});
diff --git a/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts b/plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts
similarity index 100%
rename from plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts
rename to plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts
diff --git a/plugins/events-backend-module-kafka/src/utils/config.test.ts b/plugins/events-backend-module-kafka/src/utils/config.test.ts
new file mode 100644
index 0000000000..2bd6121ba2
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/utils/config.test.ts
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 { readRetryConfig, readKafkaConfig } from './config';
+
+describe('readRetryConfig', () => {
+ it('should return empty object when config is undefined', () => {
+ const result = readRetryConfig(undefined);
+ expect(result).toEqual({});
+ });
+
+ it('should return empty object when config is empty', () => {
+ const config = new ConfigReader({});
+ const result = readRetryConfig(config);
+ expect(result).toEqual({});
+ });
+
+ it('should read retry configuration with milliseconds', () => {
+ const config = new ConfigReader({
+ maxRetryTime: { milliseconds: 20000 },
+ initialRetryTime: { milliseconds: 200 },
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+
+ const result = readRetryConfig(config);
+
+ expect(result).toEqual({
+ maxRetryTime: 20000,
+ initialRetryTime: 200,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+ });
+
+ it('should read retry configuration with string values', () => {
+ const config = new ConfigReader({
+ maxRetryTime: { seconds: 20 },
+ initialRetryTime: { minutes: 1 },
+ factor: '0.4',
+ multiplier: '4',
+ retries: '10',
+ });
+
+ const result = readRetryConfig(config);
+
+ expect(result).toEqual({
+ maxRetryTime: 20000,
+ initialRetryTime: 60000,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ });
+ });
+
+ it('should handle HumanDuration values', () => {
+ const config = new ConfigReader({
+ maxRetryTime: { hours: 1 },
+ initialRetryTime: { days: 1 },
+ });
+
+ const result = readRetryConfig(config);
+
+ expect(result).toEqual({
+ maxRetryTime: 3600000,
+ initialRetryTime: 86400000,
+ factor: undefined,
+ multiplier: undefined,
+ retries: undefined,
+ });
+ });
+
+ it('should handle partial configuration', () => {
+ const config = new ConfigReader({
+ maxRetryTime: { milliseconds: 15000 },
+ retries: 5,
+ });
+
+ const result = readRetryConfig(config);
+
+ expect(result).toEqual({
+ maxRetryTime: 15000,
+ initialRetryTime: undefined,
+ factor: undefined,
+ multiplier: undefined,
+ retries: 5,
+ });
+ });
+});
+
+describe('readKafkaConfig', () => {
+ it('should read minimal kafka configuration', () => {
+ const config = new ConfigReader({
+ clientId: 'test-client',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ });
+
+ const result = readKafkaConfig(config);
+
+ expect(result).toEqual({
+ clientId: 'test-client',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ authenticationTimeout: undefined,
+ connectionTimeout: undefined,
+ requestTimeout: undefined,
+ enforceRequestTimeout: undefined,
+ ssl: undefined,
+ sasl: undefined,
+ retry: {},
+ });
+ });
+
+ it('should read full kafka configuration with all optional fields', () => {
+ const config = new ConfigReader({
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ authenticationTimeout: { milliseconds: 20000 },
+ connectionTimeout: { milliseconds: 1500 },
+ requestTimeout: { milliseconds: 20000 },
+ enforceRequestTimeout: false,
+ ssl: true,
+ sasl: {
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ },
+ retry: {
+ maxRetryTime: { milliseconds: 20000 },
+ initialRetryTime: { milliseconds: 200 },
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ },
+ });
+
+ const result = readKafkaConfig(config);
+
+ expect(result).toEqual({
+ clientId: 'backstage-events',
+ brokers: ['kafka1:9092', 'kafka2:9092'],
+ authenticationTimeout: 20000,
+ connectionTimeout: 1500,
+ requestTimeout: 20000,
+ enforceRequestTimeout: false,
+ ssl: true,
+ sasl: {
+ mechanism: 'plain',
+ username: 'username',
+ password: 'password',
+ },
+ retry: {
+ maxRetryTime: 20000,
+ initialRetryTime: 200,
+ factor: 0.4,
+ multiplier: 4,
+ retries: 10,
+ },
+ });
+ });
+
+ it('should handle HumanDuration values for timeouts', () => {
+ const config = new ConfigReader({
+ clientId: 'test-client',
+ brokers: ['kafka:9092'],
+ authenticationTimeout: { hours: 1 },
+ connectionTimeout: { days: 1 },
+ requestTimeout: { minutes: 5 },
+ });
+
+ const result = readKafkaConfig(config);
+
+ expect(result.authenticationTimeout).toBe(3600000);
+ expect(result.connectionTimeout).toBe(86400000);
+ expect(result.requestTimeout).toBe(300000);
+ });
+
+ it('should handle complex SSL configuration', () => {
+ const config = new ConfigReader({
+ clientId: 'secure-client',
+ brokers: ['secure-kafka:9093'],
+ ssl: {
+ rejectUnauthorized: false,
+ ca: 'ca-certificate',
+ key: 'client-key',
+ cert: 'client-cert',
+ },
+ });
+
+ const result = readKafkaConfig(config);
+
+ expect(result.ssl).toEqual({
+ rejectUnauthorized: false,
+ ca: 'ca-certificate',
+ key: 'client-key',
+ cert: 'client-cert',
+ });
+ });
+
+ it('should handle complex SASL configuration', () => {
+ const config = new ConfigReader({
+ clientId: 'sasl-client',
+ brokers: ['sasl-kafka:9094'],
+ sasl: {
+ mechanism: 'scram-sha-256',
+ username: 'kafka-user',
+ password: 'kafka-password',
+ authorizationIdentity: 'authz-user',
+ },
+ });
+
+ const result = readKafkaConfig(config);
+
+ expect(result.sasl).toEqual({
+ mechanism: 'scram-sha-256',
+ username: 'kafka-user',
+ password: 'kafka-password',
+ authorizationIdentity: 'authz-user',
+ });
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/utils/config.ts b/plugins/events-backend-module-kafka/src/utils/config.ts
new file mode 100644
index 0000000000..4e140d26a3
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/utils/config.ts
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 { durationToMilliseconds } from '@backstage/types';
+import { KafkaConfig, RetryOptions } from 'kafkajs';
+
+/**
+ * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined.
+ *
+ * @param config - The configuration object to read from.
+ * @param key - The key to look up in the configuration.
+ * @returns The duration in milliseconds, or undefined if the key is not defined.
+ */
+export const readOptionalHumanDurationInMs = (
+ config: Config,
+ key: string,
+): number | undefined => {
+ const humanDuration = config.has(key)
+ ? readDurationFromConfig(config, { key })
+ : undefined;
+
+ if (!humanDuration) return undefined;
+
+ return durationToMilliseconds(humanDuration);
+};
+
+/**
+ * Reads retry configuration options from the provided config object.
+ *
+ * @param config - The configuration object to read retry options from, or undefined.
+ * @returns A RetryOptions object with optional retry settings, or an empty object if config is undefined.
+ */
+export const readRetryConfig = (config: Config | undefined): RetryOptions => {
+ if (!config) {
+ return {};
+ }
+
+ return {
+ maxRetryTime: readOptionalHumanDurationInMs(config, 'maxRetryTime'),
+ initialRetryTime: readOptionalHumanDurationInMs(config, 'initialRetryTime'),
+ factor: config.getOptionalNumber('factor'),
+ multiplier: config.getOptionalNumber('multiplier'),
+ retries: config.getOptionalNumber('retries'),
+ };
+};
+
+/**
+ * Reads Kafka configuration from the provided config object.
+ *
+ * @param config - The configuration object containing Kafka settings.
+ * @returns A KafkaConfig object with all necessary Kafka connection and authentication settings.
+ */
+export const readKafkaConfig = (config: Config): KafkaConfig => {
+ return {
+ clientId: config.getString('clientId'),
+ brokers: config.getStringArray('brokers'),
+ authenticationTimeout: readOptionalHumanDurationInMs(
+ config,
+ 'authenticationTimeout',
+ ),
+ connectionTimeout: readOptionalHumanDurationInMs(
+ config,
+ 'connectionTimeout',
+ ),
+ requestTimeout: readOptionalHumanDurationInMs(config, 'requestTimeout'),
+ enforceRequestTimeout: config.getOptionalBoolean('enforceRequestTimeout'),
+ ssl: config.getOptional('ssl') as KafkaConfig['ssl'],
+ sasl: config.getOptional('sasl') as KafkaConfig['sasl'],
+ retry: readRetryConfig(config.getOptionalConfig('retry')),
+ };
+};
diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts
new file mode 100644
index 0000000000..bfb373ea53
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { convertHeadersToMetadata, payloadToBuffer } from './kafkaTransformers';
+
+describe('kafka-transformers', () => {
+ describe('convertHeadersToMetadata', () => {
+ it('should return undefined when headers is undefined', () => {
+ const result = convertHeadersToMetadata(undefined);
+ expect(result).toBeUndefined();
+ });
+
+ it('should convert string headers to metadata', () => {
+ const headers = {
+ 'content-type': 'application/json',
+ 'user-id': '12345',
+ };
+
+ const result = convertHeadersToMetadata(headers);
+
+ expect(result).toEqual({
+ 'content-type': 'application/json',
+ 'user-id': '12345',
+ });
+ });
+
+ it('should convert Buffer headers to string metadata', () => {
+ const headers = {
+ 'content-type': Buffer.from('application/json'),
+ 'correlation-id': Buffer.from('abc-123'),
+ };
+
+ const result = convertHeadersToMetadata(headers);
+
+ expect(result).toEqual({
+ 'content-type': 'application/json',
+ 'correlation-id': 'abc-123',
+ });
+ });
+
+ it('should convert array headers to string array metadata', () => {
+ const headers = {
+ tags: ['tag1', 'tag2'],
+ 'buffer-tags': [Buffer.from('tag3'), Buffer.from('tag4')],
+ 'mixed-tags': ['tag5', Buffer.from('tag6')],
+ };
+
+ const result = convertHeadersToMetadata(headers);
+
+ expect(result).toEqual({
+ tags: ['tag1', 'tag2'],
+ 'buffer-tags': ['tag3', 'tag4'],
+ 'mixed-tags': ['tag5', 'tag6'],
+ });
+ });
+
+ it('should handle mixed header types', () => {
+ const headers = {
+ 'string-header': 'value',
+ 'buffer-header': Buffer.from('buffer-value'),
+ 'array-header': ['item1', Buffer.from('item2')],
+ 'undefined-header': undefined,
+ };
+
+ const result = convertHeadersToMetadata(headers);
+
+ expect(result).toEqual({
+ 'string-header': 'value',
+ 'buffer-header': 'buffer-value',
+ 'array-header': ['item1', 'item2'],
+ 'undefined-header': undefined,
+ });
+ });
+
+ it('should handle empty headers object', () => {
+ const headers = {};
+ const result = convertHeadersToMetadata(headers);
+ expect(result).toEqual({});
+ });
+ });
+
+ describe('payloadToBuffer', () => {
+ it('should return the same Buffer when payload is already a Buffer', () => {
+ const originalBuffer = Buffer.from('test data');
+
+ const result = payloadToBuffer(originalBuffer);
+
+ expect(result).toBe(originalBuffer);
+ expect(Buffer.isBuffer(result)).toBe(true);
+ });
+
+ it('should convert string to Buffer', () => {
+ const payload = 'hello world';
+
+ const result = payloadToBuffer(payload);
+
+ expect(Buffer.isBuffer(result)).toBe(true);
+ expect(result.toString()).toBe('hello world');
+ });
+
+ it('should convert object to JSON Buffer', () => {
+ const payload = { name: 'John', age: 30 };
+
+ const result = payloadToBuffer(payload);
+
+ expect(Buffer.isBuffer(result)).toBe(true);
+ expect(JSON.parse(result.toString())).toEqual(payload);
+ });
+
+ it('should convert array to JSON Buffer', () => {
+ const payload = [1, 2, 3, 'test'];
+
+ const result = payloadToBuffer(payload);
+
+ expect(Buffer.isBuffer(result)).toBe(true);
+ expect(JSON.parse(result.toString())).toEqual(payload);
+ });
+
+ it('should convert primitives to JSON Buffer', () => {
+ expect(payloadToBuffer(42).toString()).toBe('42');
+ expect(payloadToBuffer(true).toString()).toBe('true');
+ expect(payloadToBuffer(null).toString()).toBe('null');
+ });
+ });
+});
diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts
new file mode 100644
index 0000000000..871cb08a04
--- /dev/null
+++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { EventParams } from '@backstage/plugin-events-node';
+import { IHeaders } from 'kafkajs';
+
+type EventMetadata = EventParams['metadata'];
+
+export const convertHeadersToMetadata = (
+ headers: IHeaders | undefined,
+): EventMetadata => {
+ if (!headers) return undefined;
+
+ const metadata: EventMetadata = {};
+
+ Object.entries(headers).forEach(([key, value]) => {
+ // If value is an array use toString() on all values converting any Buffer types to valid strings
+ if (Array.isArray(value)) metadata[key] = value.map(v => v.toString());
+ // Always return the values using toString() to catch all Buffer types that should be converted to strings
+ else metadata[key] = value?.toString();
+ });
+
+ return metadata;
+};
+
+export const payloadToBuffer = (payload: unknown): Buffer => {
+ if (Buffer.isBuffer(payload)) {
+ return payload;
+ }
+
+ if (typeof payload === 'string') {
+ return Buffer.from(payload, 'utf8'); // More explicit encoding
+ }
+
+ // Convert to JSON string then encode
+ return Buffer.from(JSON.stringify(payload), 'utf8');
+};
diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md
index 91ac1aa27e..a9233cc14a 100644
--- a/plugins/events-backend-test-utils/CHANGELOG.md
+++ b/plugins/events-backend-test-utils/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-events-backend-test-utils
+## 0.1.51
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.18
+
## 0.1.51-next.0
### Patch Changes
diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json
index 64c0ee9925..814fb23241 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.51-next.0",
+ "version": "0.1.51",
"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 8663b0707b..d504f3fdaf 100644
--- a/plugins/events-backend/CHANGELOG.md
+++ b/plugins/events-backend/CHANGELOG.md
@@ -1,5 +1,40 @@
# @backstage/plugin-events-backend
+## 0.5.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-events-node@0.4.18
+
+## 0.5.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.5.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 0.5.9-next.0
### Patch Changes
diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json
index db9f5c9c72..5089655b6e 100644
--- a/plugins/events-backend/package.json
+++ b/plugins/events-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend",
- "version": "0.5.9-next.0",
+ "version": "0.5.10-next.0",
"backstage": {
"role": "backend-plugin",
"pluginId": "events",
diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md
index 7ed7f0afb1..d748102a2b 100644
--- a/plugins/events-node/CHANGELOG.md
+++ b/plugins/events-node/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-events-node
+## 0.4.18
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.4.18-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+
## 0.4.18-next.0
### Patch Changes
diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json
index 156fb4b263..c8dc226198 100644
--- a/plugins/events-node/package.json
+++ b/plugins/events-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-node",
- "version": "0.4.18-next.0",
+ "version": "0.4.18",
"description": "The plugin-events-node module for @backstage/plugin-events-backend",
"backstage": {
"role": "node-library",
@@ -65,7 +65,7 @@
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
- "msw": "^1.0.0"
+ "msw": "^2.0.0"
},
"configSchema": "config.d.ts"
}
diff --git a/plugins/events-node/src/api/DefaultEventsService.test.ts b/plugins/events-node/src/api/DefaultEventsService.test.ts
index 13a6a68fa4..a62cc24f14 100644
--- a/plugins/events-node/src/api/DefaultEventsService.test.ts
+++ b/plugins/events-node/src/api/DefaultEventsService.test.ts
@@ -17,7 +17,7 @@
import { DefaultEventsService } from './DefaultEventsService';
import { EventParams } from './EventParams';
import { EVENTS_NOTIFY_TIMEOUT_HEADER } from './EventsService';
-import { rest } from 'msw';
+import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import {
mockServices,
@@ -129,18 +129,18 @@ describe('DefaultEventsService', () => {
});
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => res(ctx.status(200)),
+ () => new HttpResponse(null, { status: 200 }),
),
- rest.get(
+ http.get(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events',
- (_req, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
+ () =>
+ HttpResponse.json(
+ {
events: [{ topic: 'test', payload: { foo: 'bar' } }],
- }),
+ },
+ { status: 200 },
),
),
);
@@ -180,38 +180,36 @@ describe('DefaultEventsService', () => {
});
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => res(ctx.status(200)),
+ () => new HttpResponse(null, { status: 200 }),
),
// The first and third calls result in a blocking 202 that is resolved after 100ms
// The second and fourth calls result in a 200 with an event
// The fifth call blocks until the end of the test
// No more than 5 calls should be made
- rest.get(
+ http.get(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events',
- (_req, res, ctx) => {
+ () => {
callCount += 1;
if (callCount === 1 || callCount === 3) {
- return res(
- ctx.status(202),
- ctx.body(
- new ReadableStream({
- start(controller) {
- setTimeout(() => controller.close(), 100);
- },
- }),
- ),
+ return new HttpResponse(
+ new ReadableStream({
+ start(controller) {
+ setTimeout(() => controller.close(), 100);
+ },
+ }),
+ { status: 202 },
);
} else if (callCount === 2 || callCount === 4) {
- return res(
- ctx.status(200),
- ctx.json({
+ return HttpResponse.json(
+ {
events: [{ topic: 'test', payload: { callCount } }],
- }),
+ },
+ { status: 200 },
);
} else if (callCount === 5) {
- return res(ctx.status(202), ctx.body(blockingStream));
+ return new HttpResponse(blockingStream, { status: 202 });
}
throw new Error(`events endpoint called too many times`);
},
@@ -268,45 +266,42 @@ describe('DefaultEventsService', () => {
});
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => res(ctx.status(200)),
+ () => new HttpResponse(null, { status: 200 }),
),
// The first and third calls result in a blocking 202 that is resolved after 100ms
// The second and fourth calls result in a 200 with an event
// The fifth call blocks until the end of the test
// No more than 5 calls should be made
- rest.get(
+ http.get(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events',
- (_req, res, ctx) => {
+ () => {
callCount += 1;
if (callCount === 1 || callCount === 3) {
- return res(
- ctx.status(202),
- ctx.body(
- new ReadableStream({
- start(controller) {
- setTimeout(() => controller.close(), 100);
- },
- }),
- ),
+ return new HttpResponse(
+ new ReadableStream({
+ start(controller) {
+ setTimeout(() => controller.close(), 100);
+ },
+ }),
+ { status: 202 },
);
} else if (callCount === 2 || callCount === 4) {
- return res(
- ctx.status(200),
- ctx.json({
+ return HttpResponse.json(
+ {
events: [{ topic: 'test', payload: { callCount } }],
- }),
+ },
+ { status: 200 },
);
} else if (callCount === 5) {
// 5th call has a timeout header so polling should proceed to the next call
- return res(
- ctx.set(EVENTS_NOTIFY_TIMEOUT_HEADER, '100'),
- ctx.status(202),
- ctx.body(blockingStream),
- );
+ return new HttpResponse(blockingStream, {
+ status: 202,
+ headers: { [EVENTS_NOTIFY_TIMEOUT_HEADER]: '100' },
+ });
} else if (callCount === 6) {
- return res(ctx.status(202), ctx.body(blockingStream));
+ return new HttpResponse(blockingStream, { status: 202 });
}
throw new Error(`events endpoint called too many times`);
},
@@ -358,11 +353,11 @@ describe('DefaultEventsService', () => {
let calledApi = false;
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => {
+ () => {
calledApi = true;
- res(ctx.status(200));
+ return new HttpResponse(null, { status: 200 });
},
),
);
@@ -393,9 +388,9 @@ describe('DefaultEventsService', () => {
});
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -432,9 +427,9 @@ describe('DefaultEventsService', () => {
});
mswServer.use(
- rest.put(
+ http.put(
'http://localhost:0/api/events/bus/v1/subscriptions/a.tester',
- (_req, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md
index 411e010d16..4d7a89509f 100644
--- a/plugins/example-todo-list-backend/CHANGELOG.md
+++ b/plugins/example-todo-list-backend/CHANGELOG.md
@@ -1,5 +1,20 @@
# @internal/plugin-todo-list-backend
+## 1.0.46
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## 1.0.46-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/errors@1.2.7
+
## 1.0.46-next.0
### Patch Changes
diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json
index 4a8e2748c2..108655947a 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.46-next.0",
+ "version": "1.0.46",
"backstage": {
"role": "backend-plugin",
"pluginId": "todo-list",
diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md
index d2db721b76..798fa8df02 100644
--- a/plugins/example-todo-list/CHANGELOG.md
+++ b/plugins/example-todo-list/CHANGELOG.md
@@ -1,5 +1,13 @@
# @internal/plugin-todo-list
+## 1.0.46
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
## 1.0.46-next.0
### Patch Changes
diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json
index 7867750b3a..ad138b4713 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.46-next.0",
+ "version": "1.0.46",
"backstage": {
"role": "frontend-plugin",
"pluginId": "todo-list",
diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md
index fe3719d120..5e6c6254d9 100644
--- a/plugins/gateway-backend/CHANGELOG.md
+++ b/plugins/gateway-backend/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-gateway-backend
+## 1.1.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 7e860dd: Updated dependency `eventsource` to `^4.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## 1.1.1-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 1.1.1-next.0
### Patch Changes
diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json
index 7d5bd658e1..4cc39faf7b 100644
--- a/plugins/gateway-backend/package.json
+++ b/plugins/gateway-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gateway-backend",
- "version": "1.1.1-next.0",
+ "version": "1.1.1",
"backstage": {
"role": "backend-plugin",
"pluginId": "gateway",
@@ -46,7 +46,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/express": "^4.17.6",
- "eventsource": "^3.0.6",
+ "eventsource": "^4.0.0",
"wait-for-expect": "^3.0.2"
}
}
diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md
index 6da0bf0084..81c9fbc1be 100644
--- a/plugins/home-react/CHANGELOG.md
+++ b/plugins/home-react/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/plugin-home-react
+## 0.1.33
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+
+## 0.1.33-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/core-plugin-api@1.12.1-next.0
+
## 0.1.33-next.0
### Patch Changes
diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json
index f94413ca64..8f69f7eab7 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.33-next.0",
+ "version": "0.1.33",
"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 29d73e946a..6a1cbcd825 100644
--- a/plugins/home/CHANGELOG.md
+++ b/plugins/home/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/plugin-home
+## 0.8.15
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-home-react@0.1.33
+ - @backstage/theme@0.7.1
+
+## 0.8.15-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/core-app-api@1.19.3-next.1
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/plugin-home-react@0.1.33-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/theme@0.7.1-next.0
+
## 0.8.15-next.0
### Patch Changes
diff --git a/plugins/home/package.json b/plugins/home/package.json
index 01c0c5bb40..5716626c36 100644
--- a/plugins/home/package.json
+++ b/plugins/home/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-home",
- "version": "0.8.15-next.0",
+ "version": "0.8.15",
"description": "A Backstage plugin that helps you build a home page",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md
index 888354fa2e..a8ebfa6f8f 100644
--- a/plugins/kubernetes-backend/CHANGELOG.md
+++ b/plugins/kubernetes-backend/CHANGELOG.md
@@ -1,5 +1,49 @@
# @backstage/plugin-kubernetes-backend
+## 0.21.0
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`,
+ as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme)
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-node@0.4.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/plugin-catalog-node@1.20.1
+
+## 0.21.0-next.2
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- fb029b6: Updated luxon types
+- Updated dependencies
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/plugin-kubernetes-node@0.4.0-next.2
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+ - @backstage/plugin-permission-common@0.9.3
+
## 0.20.5-next.1
### Patch Changes
diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json
index a4cb8e924a..2aac84c616 100644
--- a/plugins/kubernetes-backend/package.json
+++ b/plugins/kubernetes-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-backend",
- "version": "0.20.5-next.1",
+ "version": "0.21.0",
"description": "A Backstage backend plugin that integrates towards Kubernetes",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap b/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap
index f8b3e71515..64526faacb 100644
--- a/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap
+++ b/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`CatalogClusterLocator returns the aws cluster details provided by annotations 1`] = `
{
diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md
index f61279e01b..5666354a62 100644
--- a/plugins/kubernetes-cluster/CHANGELOG.md
+++ b/plugins/kubernetes-cluster/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-kubernetes-cluster
+## 0.0.32
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/plugin-kubernetes-react@0.5.14
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-permission-react@0.4.39
+
+## 0.0.32-next.2
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+ - @backstage/plugin-kubernetes-react@0.5.14-next.1
+ - @backstage/plugin-permission-react@0.4.39-next.0
+
## 0.0.32-next.1
### Patch Changes
diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json
index f5f67971f9..4e43179c0c 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.32-next.1",
+ "version": "0.0.32",
"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 902a7e1f1d..de8f93897c 100644
--- a/plugins/kubernetes-common/CHANGELOG.md
+++ b/plugins/kubernetes-common/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/plugin-kubernetes-common
+## 0.9.9
+
+### Patch Changes
+
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+
## 0.9.9-next.0
### Patch Changes
diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json
index 512ed1f7eb..db2d9c2837 100644
--- a/plugins/kubernetes-common/package.json
+++ b/plugins/kubernetes-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-common",
- "version": "0.9.9-next.0",
+ "version": "0.9.9",
"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 d3f12c3c7d..a0fad104d8 100644
--- a/plugins/kubernetes-node/CHANGELOG.md
+++ b/plugins/kubernetes-node/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/plugin-kubernetes-node
+## 0.4.0
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-kubernetes-common@0.9.9
+
+## 0.4.0-next.2
+
+### Minor Changes
+
+- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/types@1.2.2
+ - @backstage/plugin-kubernetes-common@0.9.9-next.0
+
## 0.3.7-next.1
### Patch Changes
diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json
index 9796e05212..8778449eaa 100644
--- a/plugins/kubernetes-node/package.json
+++ b/plugins/kubernetes-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-node",
- "version": "0.3.7-next.1",
+ "version": "0.4.0",
"description": "Node.js library for the kubernetes plugin",
"backstage": {
"role": "node-library",
diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md
index b260e25475..d1bb328016 100644
--- a/plugins/kubernetes-react/CHANGELOG.md
+++ b/plugins/kubernetes-react/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-kubernetes-react
+## 0.5.14
+
+### Patch Changes
+
+- f966a85: Enabled a pod terminal at GKE
+- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas
+- f15d5f1: add missing i18n support for kubernetes and kubernetes-react
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+
## 0.5.14-next.1
### Patch Changes
diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json
index da1b5f6e3e..a634594e60 100644
--- a/plugins/kubernetes-react/package.json
+++ b/plugins/kubernetes-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-react",
- "version": "0.5.14-next.1",
+ "version": "0.5.14",
"description": "Web library for the kubernetes-react plugin",
"backstage": {
"role": "web-library",
@@ -84,7 +84,7 @@
"@testing-library/react": "^16.0.0",
"@types/react": "^18.0.0",
"jest-websocket-mock": "^2.5.0",
- "msw": "^1.3.1",
+ "msw": "^2.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router-dom": "^6.3.0"
diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts
index 4698f01dfe..3e8b7aa437 100644
--- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts
+++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts
@@ -16,7 +16,7 @@
import { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider';
import { KubernetesBackendClient } from './KubernetesBackendClient';
-import { rest } from 'msw';
+import { http, HttpResponse } from 'msw';
import { UrlPatternDiscovery } from '@backstage/core-app-api';
import { setupServer } from 'msw/node';
import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils';
@@ -89,23 +89,24 @@ describe('KubernetesBackendClient', () => {
it('hits the /clusters API', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) =>
- res(ctx.json({ items: [{ name: 'cluster-a', authProvider: 'aws' }] })),
+ http.get('http://localhost:1234/api/kubernetes/clusters', () =>
+ HttpResponse.json({
+ items: [{ name: 'cluster-a', authProvider: 'aws' }],
+ }),
),
);
const clusters = await backendClient.getClusters();
- expect(clusters).toStrictEqual([
- { name: 'cluster-a', authProvider: 'aws' },
- ]);
+ expect(clusters).toEqual([{ name: 'cluster-a', authProvider: 'aws' }]);
});
it('/clusters API throws a 404 Error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) =>
- res(ctx.status(404)),
+ http.get(
+ 'http://localhost:1234/api/kubernetes/clusters',
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -117,8 +118,9 @@ describe('KubernetesBackendClient', () => {
it('/clusters API throws a 500 Error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) =>
- res(ctx.status(500)),
+ http.get(
+ 'http://localhost:1234/api/kubernetes/clusters',
+ () => new HttpResponse(null, { status: 500 }),
),
);
@@ -130,9 +132,9 @@ describe('KubernetesBackendClient', () => {
it('hits the /resources/custom/query API', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/custom/query',
- (_, res, ctx) => res(ctx.json(mockResponse)),
+ () => HttpResponse.json(mockResponse),
),
);
@@ -157,15 +159,15 @@ describe('KubernetesBackendClient', () => {
const customObject: ObjectsByEntityResponse =
await backendClient.getCustomObjectsByEntity(request);
- expect(customObject).toStrictEqual(mockResponse);
+ expect(customObject).toEqual(mockResponse);
});
it('/resources/custom/query API throws a 404 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/custom/query',
- (_, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -197,9 +199,9 @@ describe('KubernetesBackendClient', () => {
it('/resources/custom/query API throws a 500 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/custom/query',
- (_, res, ctx) => res(ctx.status(500)),
+ () => new HttpResponse(null, { status: 500 }),
),
);
@@ -231,9 +233,8 @@ describe('KubernetesBackendClient', () => {
it('hits the /services/{entityName} API', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
- 'http://localhost:1234/api/kubernetes/services/test-name',
- (_, res, ctx) => res(ctx.json(mockResponse)),
+ http.post('http://localhost:1234/api/kubernetes/services/test-name', () =>
+ HttpResponse.json(mockResponse),
),
);
@@ -250,15 +251,15 @@ describe('KubernetesBackendClient', () => {
const entityObject: ObjectsByEntityResponse =
await backendClient.getObjectsByEntity(request);
- expect(entityObject).toStrictEqual(mockResponse);
+ expect(entityObject).toEqual(mockResponse);
});
it('services/{entityName} API throws a 404 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/services/test-name',
- (_, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -282,9 +283,9 @@ describe('KubernetesBackendClient', () => {
it('services/{entityName} API throws a 500 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/services/test-name',
- (_, res, ctx) => res(ctx.status(500)),
+ () => new HttpResponse(null, { status: 500 }),
),
);
@@ -308,9 +309,9 @@ describe('KubernetesBackendClient', () => {
it('hits the /resources/workloads/query API', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/workloads/query',
- (_, res, ctx) => res(ctx.json(mockResponse)),
+ () => HttpResponse.json(mockResponse),
),
);
@@ -328,15 +329,15 @@ describe('KubernetesBackendClient', () => {
const response: ObjectsByEntityResponse =
await backendClient.getWorkloadsByEntity(request);
- expect(response).toStrictEqual(mockResponse);
+ expect(response).toEqual(mockResponse);
});
it('/resources/workloads/query API throws a 404 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/workloads/query',
- (_, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -361,9 +362,9 @@ describe('KubernetesBackendClient', () => {
it('/resources/workloads/query API throws a 500 error', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.post(
+ http.post(
'http://localhost:1234/api/kubernetes/resources/workloads/query',
- (_, res, ctx) => res(ctx.status(500)),
+ () => new HttpResponse(null, { status: 500 }),
),
);
@@ -388,12 +389,10 @@ describe('KubernetesBackendClient', () => {
describe('proxy', () => {
beforeEach(() => {
worker.use(
- rest.get(
- 'http://localhost:1234/api/kubernetes/clusters',
- (_, res, ctx) =>
- res(
- ctx.json({ items: [{ name: 'cluster-a', authProvider: 'aws' }] }),
- ),
+ http.get('http://localhost:1234/api/kubernetes/clusters', () =>
+ HttpResponse.json({
+ items: [{ name: 'cluster-a', authProvider: 'aws' }],
+ }),
),
);
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
@@ -401,20 +400,16 @@ describe('KubernetesBackendClient', () => {
it('hits the /proxy API with oidc as protocol and okta as auth provider', async () => {
worker.use(
- rest.get(
- 'http://localhost:1234/api/kubernetes/clusters',
- (_, res, ctx) =>
- res(
- ctx.json({
- items: [
- {
- name: 'cluster-a',
- authProvider: 'oidc',
- oidcTokenProvider: 'okta',
- },
- ],
- }),
- ),
+ http.get('http://localhost:1234/api/kubernetes/clusters', () =>
+ HttpResponse.json({
+ items: [
+ {
+ name: 'cluster-a',
+ authProvider: 'oidc',
+ oidcTokenProvider: 'okta',
+ },
+ ],
+ }),
),
);
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({
@@ -428,16 +423,14 @@ describe('KubernetesBackendClient', () => {
},
};
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (req, res, ctx) =>
- res(
- req.headers.get(
- 'Backstage-Kubernetes-Authorization-oidc-okta',
- ) === 'k8-token3'
- ? ctx.json(nsResponse)
- : ctx.status(403),
- ),
+ ({ request }) =>
+ request.headers.get(
+ 'Backstage-Kubernetes-Authorization-oidc-okta',
+ ) === 'k8-token3'
+ ? HttpResponse.json(nsResponse)
+ : new HttpResponse(null, { status: 403 }),
),
);
@@ -448,7 +441,7 @@ describe('KubernetesBackendClient', () => {
const response = await backendClient.proxy(request);
- await expect(response.json()).resolves.toStrictEqual(nsResponse);
+ await expect(response.json()).resolves.toEqual(nsResponse);
expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith(
'oidc.okta',
);
@@ -457,19 +450,15 @@ describe('KubernetesBackendClient', () => {
it('hits the /proxy API with serviceAccount as auth provider', async () => {
identityApi.getCredentials.mockResolvedValue({ token: 'idToken' });
worker.use(
- rest.get(
- 'http://localhost:1234/api/kubernetes/clusters',
- (_, res, ctx) =>
- res(
- ctx.json({
- items: [
- {
- name: 'cluster-a',
- authProvider: 'serviceAccount',
- },
- ],
- }),
- ),
+ http.get('http://localhost:1234/api/kubernetes/clusters', () =>
+ HttpResponse.json({
+ items: [
+ {
+ name: 'cluster-a',
+ authProvider: 'serviceAccount',
+ },
+ ],
+ }),
),
);
@@ -481,14 +470,12 @@ describe('KubernetesBackendClient', () => {
},
};
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (req, res, ctx) =>
- res(
- req.headers.get('Authorization') === 'Bearer idToken'
- ? ctx.json(nsResponse)
- : ctx.status(403),
- ),
+ ({ request }) =>
+ request.headers.get('Authorization') === 'Bearer idToken'
+ ? HttpResponse.json(nsResponse)
+ : new HttpResponse(null, { status: 403 }),
),
);
@@ -499,7 +486,7 @@ describe('KubernetesBackendClient', () => {
const response = await backendClient.proxy(request);
- await expect(response.json()).resolves.toStrictEqual(nsResponse);
+ await expect(response.json()).resolves.toEqual(nsResponse);
expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith(
'serviceAccount',
);
@@ -507,24 +494,20 @@ describe('KubernetesBackendClient', () => {
it('ignores oidcTokenProvider for non-oidc auth provider', async () => {
worker.use(
- rest.get(
- 'http://localhost:1234/api/kubernetes/clusters',
- (_, res, ctx) =>
- res(
- ctx.json({
- items: [
- {
- name: 'cluster-a',
- authProvider: 'not oidc',
- oidcTokenProvider: 'should be ignored',
- },
- ],
- }),
- ),
+ http.get('http://localhost:1234/api/kubernetes/clusters', () =>
+ HttpResponse.json({
+ items: [
+ {
+ name: 'cluster-a',
+ authProvider: 'not oidc',
+ oidcTokenProvider: 'should be ignored',
+ },
+ ],
+ }),
),
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (_, res, ctx) => res(ctx.json([])),
+ () => HttpResponse.json([]),
),
);
@@ -552,15 +535,13 @@ describe('KubernetesBackendClient', () => {
},
};
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (req, res, ctx) =>
- res(
- req.headers.get('Backstage-Kubernetes-Authorization-aws') ===
- 'k8-token'
- ? ctx.json(nsResponse)
- : ctx.status(403),
- ),
+ ({ request }) =>
+ request.headers.get('Backstage-Kubernetes-Authorization-aws') ===
+ 'k8-token'
+ ? HttpResponse.json(nsResponse)
+ : new HttpResponse(null, { status: 403 }),
),
);
@@ -570,7 +551,7 @@ describe('KubernetesBackendClient', () => {
};
const response = await backendClient.proxy(request);
- await expect(response.json()).resolves.toStrictEqual(nsResponse);
+ await expect(response.json()).resolves.toEqual(nsResponse);
});
it('/proxy API throws a 404 error', async () => {
@@ -578,9 +559,9 @@ describe('KubernetesBackendClient', () => {
token: 'k8-token',
});
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (_, res, ctx) => res(ctx.status(404)),
+ () => new HttpResponse(null, { status: 404 }),
),
);
@@ -616,15 +597,13 @@ describe('KubernetesBackendClient', () => {
},
};
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces',
- (req, res, ctx) =>
- res(
- req.headers.get('Backstage-Kubernetes-Authorization') ===
- 'Bearer k8-token'
- ? ctx.json(nsResponse)
- : ctx.status(403),
- ),
+ ({ request }) =>
+ request.headers.get('Backstage-Kubernetes-Authorization') ===
+ 'Bearer k8-token'
+ ? HttpResponse.json(nsResponse)
+ : new HttpResponse(null, { status: 403 }),
),
);
@@ -646,14 +625,12 @@ describe('KubernetesBackendClient', () => {
},
};
worker.use(
- rest.get(
+ http.get(
'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces/new-ns',
- (req, res, ctx) =>
- res(
- req.headers.get('Backstage-Kubernetes-Authorization')
- ? ctx.status(403)
- : ctx.json(nsResponse),
- ),
+ ({ request }) =>
+ request.headers.get('Backstage-Kubernetes-Authorization')
+ ? new HttpResponse(null, { status: 403 })
+ : HttpResponse.json(nsResponse),
),
);
kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({});
@@ -663,7 +640,7 @@ describe('KubernetesBackendClient', () => {
path: '/api/v1/namespaces/new-ns',
});
- await expect(response.json()).resolves.toStrictEqual(nsResponse);
+ await expect(response.json()).resolves.toEqual(nsResponse);
});
});
});
diff --git a/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts b/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts
index 533e966137..12de678718 100644
--- a/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts
+++ b/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts
@@ -21,11 +21,6 @@ describe('HeadlampClusterLinksFormatter', () => {
beforeEach(() => {
formatter = new HeadlampClusterLinksFormatter();
- // Mock window.location.origin
- Object.defineProperty(window, 'location', {
- value: { origin: 'http://localhost:3000' },
- writable: true,
- });
});
it('formats internal dashboard link correctly', async () => {
@@ -41,7 +36,7 @@ describe('HeadlampClusterLinksFormatter', () => {
const result = await formatter.formatClusterLink(options);
expect(result.toString()).toBe(
- 'http://localhost:3000/headlamp?to=%2Fc%2Ftest-cluster%2Fpods%2Fdefault%2Ftest-pod',
+ 'http://localhost/headlamp?to=%2Fc%2Ftest-cluster%2Fpods%2Fdefault%2Ftest-pod',
);
});
diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md
index 3780dee9c6..3aeaa369fa 100644
--- a/plugins/kubernetes/CHANGELOG.md
+++ b/plugins/kubernetes/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-kubernetes
+## 0.12.14
+
+### Patch Changes
+
+- f15d5f1: add missing i18n support for kubernetes and kubernetes-react
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-kubernetes-react@0.5.14
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/plugin-kubernetes-common@0.9.9
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/plugin-permission-react@0.4.39
+
## 0.12.14-next.1
### Patch Changes
diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json
index a455747640..33692f5886 100644
--- a/plugins/kubernetes/package.json
+++ b/plugins/kubernetes/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes",
- "version": "0.12.14-next.1",
+ "version": "0.12.14",
"description": "A Backstage plugin that integrates towards Kubernetes",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md
index 78341c0273..c91b47584c 100644
--- a/plugins/mcp-actions-backend/CHANGELOG.md
+++ b/plugins/mcp-actions-backend/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/plugin-mcp-actions-backend
+## 0.1.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-client@1.12.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1
+
+## 0.1.6
+
+### Patch Changes
+
+- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+
+## 0.1.6-next.1
+
+### Patch Changes
+
+- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+
## 0.1.6-next.0
### Patch Changes
diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json
index da90397438..b99cb48afe 100644
--- a/plugins/mcp-actions-backend/package.json
+++ b/plugins/mcp-actions-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-mcp-actions-backend",
- "version": "0.1.6-next.0",
+ "version": "0.1.7-next.0",
"backstage": {
"role": "backend-plugin",
"pluginId": "mcp-actions",
@@ -40,7 +40,8 @@
"@backstage/errors": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/types": "workspace:^",
- "@modelcontextprotocol/sdk": "^1.12.3",
+ "@cfworker/json-schema": "^4.1.1",
+ "@modelcontextprotocol/sdk": "^1.24.3",
"express": "^4.22.0",
"express-promise-router": "^4.1.0",
"zod": "^3.22.4"
diff --git a/plugins/mui-to-bui/CHANGELOG.md b/plugins/mui-to-bui/CHANGELOG.md
index 81ccaeb013..7f4a45c885 100644
--- a/plugins/mui-to-bui/CHANGELOG.md
+++ b/plugins/mui-to-bui/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-mui-to-bui
+## 0.2.3-next.0
+
+### Patch Changes
+
+- e4a1180: Updated tokens from `--bui-bg` to `--bui-bg-surface-0`
+- Updated dependencies
+ - @backstage/ui@0.11.0-next.0
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/theme@0.7.1
+
+## 0.2.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/ui@0.10.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+
## 0.2.2-next.1
### Patch Changes
diff --git a/plugins/mui-to-bui/package.json b/plugins/mui-to-bui/package.json
index f5f014a9ed..2305eec5a3 100644
--- a/plugins/mui-to-bui/package.json
+++ b/plugins/mui-to-bui/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-mui-to-bui",
- "version": "0.2.2-next.1",
+ "version": "0.2.3-next.0",
"backstage": {
"role": "frontend-plugin",
"pluginId": "mui-to-bui",
diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts
index 6ba3f7d2d9..48bb318a71 100644
--- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts
+++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts
@@ -59,7 +59,7 @@ describe('convertMuiToBuiTheme', () => {
// Border radius tokens are only generated when radius is 0
expect(result.css).not.toContain('--bui-radius-3:');
// Background default maps to surface-2
- expect(result.css).toContain('--bui-bg: #f5f5f5;');
+ expect(result.css).toContain('--bui-bg-surface-0: #f5f5f5;');
expect(result.css).toContain('--bui-bg-surface-2: #f5f5f5;');
expect(result.css).toContain('--bui-bg-surface-1: #ffffff;');
expect(result.css).toContain('--bui-fg-primary: #000000;');
@@ -98,7 +98,7 @@ describe('convertMuiToBuiTheme', () => {
expect(result.css).toContain("[data-theme-mode='dark'] {");
// Background default maps to surface-2 in dark mode as well
- expect(result.css).toContain('--bui-bg: #121212;');
+ expect(result.css).toContain('--bui-bg-surface-0: #121212;');
expect(result.css).toContain('--bui-bg-surface-2: #121212;');
expect(result.css).toContain('--bui-bg-surface-1: #1e1e1e;');
expect(result.css).toContain('--bui-fg-primary: #ffffff;');
diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts
index 1b8528922b..029d70cec7 100644
--- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts
+++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts
@@ -110,9 +110,10 @@ function generateBuiVariables(theme: Mui5Theme): Record {
// Generate surface colors
Object.entries({
- '': palette.background.default,
+ 'surface-0': palette.background.default,
'surface-1': palette.background.paper,
'surface-2': palette.background.default,
+ 'surface-3': palette.background.default,
solid: palette.primary.main,
'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5),
'solid-pressed': palette.primary.dark,
@@ -125,7 +126,7 @@ function generateBuiVariables(theme: Mui5Theme): Record {
warning: palette.warning.light,
success: palette.success.light,
}).forEach(([key, value]) => {
- styleObject[`--bui-bg${key ? `-${key}` : ''}`] = value;
+ styleObject[`--bui-bg-${key}`] = value;
});
// Border colors
diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md
index feade91398..21cb61c4c4 100644
--- a/plugins/notifications-backend-module-email/CHANGELOG.md
+++ b/plugins/notifications-backend-module-email/CHANGELOG.md
@@ -1,5 +1,49 @@
# @backstage/plugin-notifications-backend-module-email
+## 0.3.17
+
+### Patch Changes
+
+- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update.
+
+ The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes:
+
+ ```diff
+ notifications:
+ processors:
+ email:
+ transportConfig:
+ transport: "ses"
+ region: "us-west-2"
+ sender: "sender@mycompany.com"
+ replyTo: "no-reply@mycompany.com"
+ sesConfig:
+ - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com"
+ fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com"
+ ```
+
+- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## 0.3.17-next.1
+
+### Patch Changes
+
+- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/integration-aws-node@0.1.19
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
## 0.3.17-next.0
### Patch Changes
diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md
index c1ead48876..248bb2c501 100644
--- a/plugins/notifications-backend-module-email/README.md
+++ b/plugins/notifications-backend-module-email/README.md
@@ -81,7 +81,6 @@ notifications:
receiver: 'users'
# Optional SES config
# sesConfig:
- # sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com'
# fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com'
# configurationSetName: 'custom-config'
# How many emails to send concurrently, defaults to 2
diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts
index 7116826c5d..55a751f8fa 100644
--- a/plugins/notifications-backend-module-email/config.d.ts
+++ b/plugins/notifications-backend-module-email/config.d.ts
@@ -136,10 +136,6 @@ export interface Config {
* Optional SES config for mail options. Allows for delegated sender
*/
sesConfig?: {
- /**
- * ARN of the identity to use as the source of the email
- */
- sourceArn?: string;
/**
* ARN of the identity to use for the "From"/sender address of the email
*/
diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json
index e62ae5e92f..b1f2831758 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.3.17-next.0",
+ "version": "0.3.17",
"description": "The email backend module for the notifications plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts
index ebe1b90f35..ad0817521f 100644
--- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts
+++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts
@@ -497,9 +497,13 @@ describe('NotificationsEmailProcessor', () => {
text: 'https://example.org/notifications',
to: 'mock@backstage.io',
ses: {
- SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com',
- FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com',
+ FromEmailAddressIdentityArn:
+ 'arn:aws:ses:us-west-2:123456789012:identity/example.com',
},
});
+
+ expect(logger.warn).toHaveBeenCalledWith(
+ 'sourceArn is not supported in SESv2 and will be ignored',
+ );
});
});
diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts
index eadfe7747e..e0e27232c2 100644
--- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts
+++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts
@@ -45,6 +45,7 @@ import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node';
import { NotificationTemplateRenderer } from '../extensions';
import Mail from 'nodemailer/lib/mailer';
import pThrottle from 'p-throttle';
+import { SendEmailCommandInput } from '@aws-sdk/client-sesv2';
export class NotificationsEmailProcessor implements NotificationProcessor {
private transporter: any;
@@ -53,6 +54,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
private readonly sender: string;
private readonly replyTo?: string;
private readonly sesConfig?: Config;
+ private readonly sesOptions?: Partial;
private readonly cacheTtl: number;
private readonly concurrencyLimit: number;
private readonly throttleInterval: number;
@@ -91,6 +93,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
this.sender = emailProcessorConfig.getString('sender');
this.replyTo = emailProcessorConfig.getOptionalString('replyTo');
this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig');
+ this.sesOptions = this.getSesOptions();
this.concurrencyLimit =
emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2;
this.throttleInterval = emailProcessorConfig.has('throttleInterval')
@@ -307,20 +310,23 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
return contentParts.join('\n\n');
}
- private async getSesOptions() {
+ private getSesOptions(): Partial | undefined {
if (!this.sesConfig) {
return undefined;
}
- const ses: Record = {};
- const sourceArn = this.sesConfig.getOptionalString('sourceArn');
+ const ses: Partial = {};
const fromArn = this.sesConfig.getOptionalString('fromArn');
+ const sourceArn = this.sesConfig.getOptionalString('sourceArn');
const configurationSetName = this.sesConfig.getOptionalString(
'configurationSetName',
);
- if (sourceArn) ses.SourceArn = sourceArn;
- if (fromArn) ses.FromArn = fromArn;
+ if (fromArn) ses.FromEmailAddressIdentityArn = fromArn;
if (configurationSetName) ses.ConfigurationSetName = configurationSetName;
+ if (sourceArn)
+ this.logger.warn(
+ 'sourceArn is not supported in SESv2 and will be ignored',
+ );
return Object.keys(ses).length > 0 ? ses : undefined;
}
@@ -332,7 +338,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
html: this.getHtmlContent(notification),
text: this.getTextContent(notification),
replyTo: this.replyTo,
- ses: await this.getSesOptions(),
+ ses: this.sesOptions,
};
await this.sendMails(mailOptions, emails);
@@ -350,7 +356,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
html: await this.templateRenderer?.getHtml?.(notification),
text: await this.templateRenderer?.getText?.(notification),
replyTo: this.replyTo,
- ses: await this.getSesOptions(),
+ ses: this.sesOptions,
};
await this.sendMails(mailOptions, emails);
diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md
index 9000ef2203..4d9c711297 100644
--- a/plugins/notifications-backend-module-slack/CHANGELOG.md
+++ b/plugins/notifications-backend-module-slack/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-notifications-backend-module-slack
+## 0.3.0
+
+### Minor Changes
+
+- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics.
+
+### Patch Changes
+
+- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options.
+- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## 0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
## 0.2.2-next.0
### Patch Changes
diff --git a/plugins/notifications-backend-module-slack/config.d.ts b/plugins/notifications-backend-module-slack/config.d.ts
index 8cd450940e..17a4e25e0d 100644
--- a/plugins/notifications-backend-module-slack/config.d.ts
+++ b/plugins/notifications-backend-module-slack/config.d.ts
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { HumanDuration } from '@backstage/types';
+
export interface Config {
notifications?: {
processors?: {
@@ -26,8 +28,40 @@ export interface Config {
* Broadcast notification receivers when receiver is set to config
* These can be Slack User IDs, Slack User Email addresses, Slack Channel
* Names, or Slack Channel IDs. Any valid identifier that chat.postMessage can accept.
+ * @deprecated Use broadcastRoutes instead for more granular control
*/
broadcastChannels?: string[];
+ /**
+ * Optional username to display as the sender of the notification
+ */
+ username?: string;
+ /**
+ * Routes for broadcast notifications based on origin and/or topic.
+ * Routes are evaluated in order, first match wins.
+ * Origin+topic matches take precedence over origin-only matches.
+ */
+ broadcastRoutes?: Array<{
+ /**
+ * The origin to match (e.g., 'plugin:catalog', 'external:my-service')
+ */
+ origin?: string;
+ /**
+ * The topic to match (e.g., 'entity-updated', 'alerts')
+ */
+ topic?: string;
+ /**
+ * The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs.
+ */
+ channel: string | string[];
+ }>;
+ /**
+ * Concurrency limit for Slack notifications per backend instance of the notifications plugin, defaults to 10.
+ */
+ concurrencyLimit?: number;
+ /**
+ * Throttle duration between Slack notifications per backend instance of the notifications plugin, defaults to 1 minute.
+ */
+ throttleInterval?: HumanDuration | string;
}>;
};
};
diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json
index 9cb2486d3e..0e35577006 100644
--- a/plugins/notifications-backend-module-slack/package.json
+++ b/plugins/notifications-backend-module-slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications-backend-module-slack",
- "version": "0.2.2-next.0",
+ "version": "0.3.0",
"description": "The slack backend module for the notifications plugin.",
"backstage": {
"role": "backend-plugin-module",
@@ -54,7 +54,7 @@
"@backstage/cli": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/test-utils": "workspace:^",
- "@faker-js/faker": "^8.4.1",
+ "@faker-js/faker": "^10.0.0",
"msw": "^2.0.0"
},
"configSchema": "config.d.ts"
diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts
index ad4bf01e9f..9490ff7dde 100644
--- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts
+++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts
@@ -19,6 +19,22 @@ import { SlackNotificationProcessor } from './SlackNotificationProcessor';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
import { WebClient } from '@slack/web-api';
import { Entity } from '@backstage/catalog-model';
+import pThrottle from 'p-throttle';
+import { durationToMilliseconds } from '@backstage/types';
+
+const throttleConfigs: Array<{ limit: number; interval: number }> = [];
+
+jest.mock('p-throttle', () => ({
+ __esModule: true,
+ default: jest.fn((config: { limit: number; interval: number }) => {
+ throttleConfigs.push(config);
+ return Promise | any>(fn: T) =>
+ (...args: Parameters) =>
+ Promise.resolve(fn(...args));
+ }),
+}));
+
+const mockedPThrottle = pThrottle as jest.MockedFunction;
jest.mock('@slack/web-api', () => {
const mockSlack = {
@@ -128,6 +144,8 @@ describe('SlackNotificationProcessor', () => {
beforeEach(() => {
jest.clearAllMocks();
+ throttleConfigs.length = 0;
+ mockedPThrottle.mockClear();
});
it('should send a notification to a group', async () => {
@@ -393,6 +411,462 @@ describe('SlackNotificationProcessor', () => {
});
});
+ describe('when broadcast routes are configured', () => {
+ it('should route by origin and topic (highest priority)', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastChannels: ['default-channel'],
+ broadcastRoutes: [
+ { origin: 'plugin:catalog', channel: 'catalog-channel' },
+ { topic: 'alerts', channel: 'alerts-channel' },
+ {
+ origin: 'plugin:catalog',
+ topic: 'alerts',
+ channel: 'catalog-alerts-channel',
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:catalog',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'alerts',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'alerts' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'catalog-alerts-channel' }),
+ );
+ });
+
+ it('should route by origin only when no origin+topic match', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ { origin: 'plugin:catalog', channel: 'catalog-channel' },
+ { topic: 'alerts', channel: 'alerts-channel' },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:catalog',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'updates',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'updates' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'catalog-channel' }),
+ );
+ });
+
+ it('should route by topic only when no origin match', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ { origin: 'plugin:catalog', channel: 'catalog-channel' },
+ { topic: 'alerts', channel: 'alerts-channel' },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:unknown',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'alerts',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'alerts' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'alerts-channel' }),
+ );
+ });
+
+ it('should fall back to broadcastChannels when no route matches', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastChannels: ['default-channel'],
+ broadcastRoutes: [
+ { origin: 'plugin:catalog', channel: 'catalog-channel' },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:unknown',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'default-channel' }),
+ );
+ });
+
+ it('should support multiple channels in a route', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ {
+ origin: 'plugin:catalog',
+ channel: ['channel1', 'channel2', 'channel3'],
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:catalog',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(3);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'channel1' }),
+ );
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'channel2' }),
+ );
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'channel3' }),
+ );
+ });
+
+ it('should support single channel as string in a route', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ { topic: 'alerts', channel: 'single-alerts-channel' },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:any',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'alerts',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'alerts' },
+ },
+ );
+
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'single-alerts-channel' }),
+ );
+ });
+
+ it('should not send when no route matches and no broadcastChannels configured', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ { origin: 'plugin:catalog', channel: 'catalog-channel' },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:unknown',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'unknown-topic',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'unknown-topic' },
+ },
+ );
+
+ expect(slack.chat.postMessage).not.toHaveBeenCalled();
+ });
+
+ it('should match origin+topic route over origin-only route', async () => {
+ const slack = new WebClient();
+ const routesConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ broadcastRoutes: [
+ // Origin-only route defined first
+ { origin: 'plugin:catalog', channel: 'general-catalog' },
+ // More specific origin+topic route defined second
+ {
+ origin: 'plugin:catalog',
+ topic: 'entity-deleted',
+ channel: 'catalog-deletions',
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(routesConfig, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.postProcess(
+ {
+ origin: 'plugin:catalog',
+ id: '1234',
+ user: null,
+ created: new Date(),
+ payload: {
+ title: 'notification',
+ topic: 'entity-deleted',
+ },
+ },
+ {
+ recipients: { type: 'broadcast' },
+ payload: { title: 'notification', topic: 'entity-deleted' },
+ },
+ );
+
+ // Should use the origin+topic match, not the origin-only match
+ expect(slack.chat.postMessage).toHaveBeenCalledTimes(1);
+ expect(slack.chat.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({ channel: 'catalog-deletions' }),
+ );
+ });
+ });
+
describe('when slack.com/bot-notify annotation is missing', () => {
it('should not send notification to a group without annotation', async () => {
const slack = new WebClient();
@@ -953,4 +1427,81 @@ describe('SlackNotificationProcessor', () => {
);
});
});
+
+ describe('when throttling is not configured', () => {
+ it('should use default concurrency limit of 10 per minute', async () => {
+ const slack = new WebClient();
+
+ const processor = SlackNotificationProcessor.fromConfig(config, {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ })[0];
+
+ await processor.processOptions({
+ recipients: { type: 'entity', entityRef: 'group:default/mock' },
+ payload: { title: 'notification' },
+ });
+
+ expect(slack.chat.postMessage).toHaveBeenCalled();
+ expect(throttleConfigs).toEqual([
+ {
+ limit: 10,
+ interval: durationToMilliseconds({ minutes: 1 }),
+ },
+ ]);
+ });
+ });
+
+ describe('when throttling is configured', () => {
+ it('should use custom concurrency limit and interval values', async () => {
+ const slack = new WebClient();
+ const throttlingConfig = mockServices.rootConfig({
+ data: {
+ app: {
+ baseUrl: 'https://example.org',
+ },
+ notifications: {
+ processors: {
+ slack: [
+ {
+ token: 'mock-token',
+ concurrencyLimit: 5,
+ throttleInterval: 'PT30S',
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const processor = SlackNotificationProcessor.fromConfig(
+ throttlingConfig,
+ {
+ auth,
+ logger,
+ catalog: catalogServiceMock({
+ entities: DEFAULT_ENTITIES_RESPONSE.items,
+ }),
+ slack,
+ },
+ )[0];
+
+ await processor.processOptions({
+ recipients: { type: 'entity', entityRef: 'group:default/mock' },
+ payload: { title: 'notification' },
+ });
+
+ expect(slack.chat.postMessage).toHaveBeenCalled();
+ expect(throttleConfigs).toEqual([
+ {
+ limit: 5,
+ interval: durationToMilliseconds({ seconds: 30 }),
+ },
+ ]);
+ });
+ });
});
diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts
index f9b00c9a58..e8f8d9eda4 100644
--- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts
+++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts
@@ -21,7 +21,7 @@ import {
parseEntityRef,
UserEntity,
} from '@backstage/catalog-model';
-import { Config } from '@backstage/config';
+import { Config, readDurationFromConfig } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { Notification } from '@backstage/plugin-notifications-common';
import {
@@ -34,6 +34,7 @@ import { ChatPostMessageArguments, WebClient } from '@slack/web-api';
import DataLoader from 'dataloader';
import pThrottle from 'p-throttle';
import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants';
+import { BroadcastRoute } from './types';
import { ExpiryMap, toChatPostMessageArgs } from './util';
import { CatalogService } from '@backstage/plugin-catalog-node';
@@ -48,8 +49,11 @@ export class SlackNotificationProcessor implements NotificationProcessor {
private readonly messagesSent: Counter;
private readonly messagesFailed: Counter;
private readonly broadcastChannels?: string[];
+ private readonly broadcastRoutes?: BroadcastRoute[];
private readonly entityLoader: DataLoader;
private readonly username?: string;
+ private readonly concurrencyLimit: number;
+ private readonly throttleInterval: number;
static fromConfig(
config: Config,
@@ -68,10 +72,23 @@ export class SlackNotificationProcessor implements NotificationProcessor {
const slack = options.slack ?? new WebClient(token);
const broadcastChannels = c.getOptionalStringArray('broadcastChannels');
const username = c.getOptionalString('username');
+ const broadcastRoutesConfig = c.getOptionalConfigArray('broadcastRoutes');
+ const broadcastRoutes = broadcastRoutesConfig?.map(route =>
+ this.parseBroadcastRoute(route),
+ );
+ const concurrencyLimit = c.getOptionalNumber('concurrencyLimit') ?? 10;
+ const throttleInterval = c.has('throttleInterval')
+ ? durationToMilliseconds(
+ readDurationFromConfig(c, { key: 'throttleInterval' }),
+ )
+ : durationToMilliseconds({ minutes: 1 });
return new SlackNotificationProcessor({
slack,
broadcastChannels,
+ broadcastRoutes,
username,
+ concurrencyLimit,
+ throttleInterval,
...options,
});
});
@@ -83,16 +100,32 @@ export class SlackNotificationProcessor implements NotificationProcessor {
logger: LoggerService;
catalog: CatalogService;
broadcastChannels?: string[];
+ broadcastRoutes?: BroadcastRoute[];
username?: string;
+ concurrencyLimit?: number;
+ throttleInterval?: number;
}) {
- const { auth, catalog, logger, slack, broadcastChannels, username } =
- options;
+ const {
+ auth,
+ catalog,
+ logger,
+ slack,
+ broadcastChannels,
+ broadcastRoutes,
+ username,
+ concurrencyLimit,
+ throttleInterval,
+ } = options;
this.logger = logger;
this.catalog = catalog;
this.auth = auth;
this.slack = slack;
this.broadcastChannels = broadcastChannels;
+ this.broadcastRoutes = broadcastRoutes;
this.username = username;
+ this.concurrencyLimit = concurrencyLimit ?? 10;
+ this.throttleInterval =
+ throttleInterval ?? durationToMilliseconds({ minutes: 1 });
this.entityLoader = new DataLoader(
async entityRefs => {
@@ -134,8 +167,8 @@ export class SlackNotificationProcessor implements NotificationProcessor {
);
const throttle = pThrottle({
- limit: 10,
- interval: durationToMilliseconds({ minutes: 1 }),
+ limit: this.concurrencyLimit,
+ interval: this.throttleInterval,
});
const throttled = throttle((opts: ChatPostMessageArguments) =>
this.sendNotification(opts),
@@ -235,7 +268,8 @@ export class SlackNotificationProcessor implements NotificationProcessor {
// Handle broadcast case
if (notification.user === null) {
- destinations.push(...(this.broadcastChannels ?? []));
+ const routedChannels = this.getBroadcastDestinations(notification);
+ destinations.push(...routedChannels);
} else if (options.recipients.type === 'entity') {
// Handle user-specific notification
const entityRefs = [options.recipients.entityRef].flat();
@@ -384,4 +418,88 @@ export class SlackNotificationProcessor implements NotificationProcessor {
throw new Error(`Failed to send notification: ${response.error}`);
}
}
+
+ private static parseBroadcastRoute(route: Config): BroadcastRoute {
+ const channelValue = route.getOptional('channel');
+ let channels: string[];
+
+ if (typeof channelValue === 'string') {
+ channels = [channelValue];
+ } else if (Array.isArray(channelValue)) {
+ channels = channelValue as string[];
+ } else {
+ throw new Error(
+ 'broadcastRoutes entry must have a channel property (string or string[])',
+ );
+ }
+
+ return {
+ origin: route.getOptionalString('origin'),
+ topic: route.getOptionalString('topic'),
+ channels,
+ };
+ }
+
+ /**
+ * Gets the destination channels for a broadcast notification based on
+ * configured routes. Routes are matched by origin and/or topic.
+ *
+ * Matching precedence:
+ * 1. Routes with both origin AND topic matching (most specific)
+ * 2. Routes with only origin matching
+ * 3. Routes with only topic matching
+ * 4. Default broadcastChannels (least specific fallback)
+ *
+ * The first matching route wins within each precedence level.
+ */
+ private getBroadcastDestinations(notification: Notification): string[] {
+ const { origin } = notification;
+ const { topic } = notification.payload;
+
+ if (!this.broadcastRoutes || this.broadcastRoutes.length === 0) {
+ // Fall back to legacy broadcastChannels config
+ return this.broadcastChannels ?? [];
+ }
+
+ // Find most specific match
+ // Priority 1: origin AND topic match
+ const originAndTopicMatch = this.broadcastRoutes.find(
+ route =>
+ route.origin !== undefined &&
+ route.topic !== undefined &&
+ route.origin === origin &&
+ route.topic === topic,
+ );
+
+ if (originAndTopicMatch) {
+ return originAndTopicMatch.channels;
+ }
+
+ // Priority 2: origin-only match (no topic specified in route)
+ const originOnlyMatch = this.broadcastRoutes.find(
+ route =>
+ route.origin !== undefined &&
+ route.topic === undefined &&
+ route.origin === origin,
+ );
+
+ if (originOnlyMatch) {
+ return originOnlyMatch.channels;
+ }
+
+ // Priority 3: topic-only match (no origin specified in route)
+ const topicOnlyMatch = this.broadcastRoutes.find(
+ route =>
+ route.topic !== undefined &&
+ route.origin === undefined &&
+ route.topic === topic,
+ );
+
+ if (topicOnlyMatch) {
+ return topicOnlyMatch.channels;
+ }
+
+ // No match found, fall back to legacy broadcastChannels
+ return this.broadcastChannels ?? [];
+ }
}
diff --git a/plugins/notifications-backend-module-slack/src/lib/types.ts b/plugins/notifications-backend-module-slack/src/lib/types.ts
index bd87e9656c..316df03462 100644
--- a/plugins/notifications-backend-module-slack/src/lib/types.ts
+++ b/plugins/notifications-backend-module-slack/src/lib/types.ts
@@ -18,3 +18,16 @@ export interface SlackNotificationOptions {
url: string;
payload: string;
}
+
+/**
+ * Configuration for routing broadcast notifications to specific Slack channels
+ * based on origin and/or topic.
+ */
+export type BroadcastRoute = {
+ /** The origin to match (e.g., 'plugin:catalog', 'external:my-service') */
+ origin?: string;
+ /** The topic to match (e.g., 'entity-updated', 'alerts') */
+ topic?: string;
+ /** The Slack channel(s) to send to */
+ channels: string[];
+};
diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md
index 7ae71e5b23..91da5468ca 100644
--- a/plugins/notifications-backend/CHANGELOG.md
+++ b/plugins/notifications-backend/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-notifications-backend
+## 0.6.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-notifications-node@0.2.22
+
+## 0.6.1-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+
## 0.6.1-next.0
### Patch Changes
diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json
index 5e50a6b787..7262b65d38 100644
--- a/plugins/notifications-backend/package.json
+++ b/plugins/notifications-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications-backend",
- "version": "0.6.1-next.0",
+ "version": "0.6.1",
"backstage": {
"role": "backend-plugin",
"pluginId": "notifications",
diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md
index 97cd9b2585..98d1b3c84c 100644
--- a/plugins/notifications-node/CHANGELOG.md
+++ b/plugins/notifications-node/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-notifications-node
+## 0.2.22
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.2.22-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-signals-node@0.1.27-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/plugin-notifications-common@0.2.0
+
## 0.2.22-next.0
### Patch Changes
diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json
index 25d7b2f9ca..8007852dd5 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.22-next.0",
+ "version": "0.2.22",
"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 b40d8540bf..50bd9b35a3 100644
--- a/plugins/notifications/CHANGELOG.md
+++ b/plugins/notifications/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-notifications
+## 0.5.12
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- 53347cc: Move long notification descriptions behind `Show more/less` button.
+
+ This improves readability of the notifications list by preventing long descriptions from taking up too much space
+ or rendering very small scrollable areas.
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-signals-react@0.0.18
+
## 0.5.12-next.0
### Patch Changes
diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json
index 5f2c652425..1a6b544da3 100644
--- a/plugins/notifications/package.json
+++ b/plugins/notifications/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications",
- "version": "0.5.12-next.0",
+ "version": "0.5.12",
"backstage": {
"role": "frontend-plugin",
"pluginId": "notifications",
diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx
index 7e3f519b0c..e8fa422fa1 100644
--- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx
+++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx
@@ -14,14 +14,11 @@
* limitations under the License.
*/
import { ChangeEvent } from 'react';
-
-import Divider from '@material-ui/core/Divider';
import FormControl from '@material-ui/core/FormControl';
import Grid from '@material-ui/core/Grid';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
-import Typography from '@material-ui/core/Typography';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
@@ -200,11 +197,6 @@ export const NotificationsFilters = ({
return (
<>
-
- Filters
-
-
-
View
diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
index 739e9a1535..69242ac60e 100644
--- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
+++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useState, useMemo, useEffect } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import throttle from 'lodash/throttle';
import {
Content,
@@ -156,13 +156,17 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
const isUnread = !!value?.[1]?.unread;
const allTopics = value?.[2]?.topics;
- let tableTitle = `All notifications (${totalCount})`;
+ let tableTitle = `All notifications `;
if (saved) {
- tableTitle = `Saved notifications (${totalCount})`;
+ tableTitle = `Saved notifications`;
} else if (unreadOnly === true) {
- tableTitle = `Unread notifications (${totalCount})`;
+ tableTitle = `Unread notifications`;
} else if (unreadOnly === false) {
- tableTitle = `Read notifications (${totalCount})`;
+ tableTitle = `Read notifications`;
+ }
+
+ if (totalCount) {
+ tableTitle += ` (${totalCount})`;
}
return (
diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx
new file mode 100644
index 0000000000..762900c2b9
--- /dev/null
+++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import Typography from '@material-ui/core/Typography';
+import Button from '@material-ui/core/Button';
+import { useState } from 'react';
+
+const MAX_LENGTH = 100;
+
+export const NotificationDescription = (props: { description: string }) => {
+ const { description } = props;
+ const [shown, setShown] = useState(false);
+ const isLong = description.length > MAX_LENGTH;
+
+ if (!isLong) {
+ return {description};
+ }
+
+ if (shown) {
+ return (
+
+ {description}{' '}
+
+
+ );
+ }
+ return (
+
+ {description.substring(0, MAX_LENGTH)}...{' '}
+
+
+ );
+};
diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx
index 8ec237e62f..952c18f9f9 100644
--- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx
+++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { useState, useCallback, useMemo, useEffect } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import throttle from 'lodash/throttle';
// @ts-ignore
import RelativeTime from 'react-relative-time';
@@ -37,14 +37,11 @@ import { notificationsApiRef } from '../../api';
import { SelectAll } from './SelectAll';
import { BulkActions } from './BulkActions';
import { NotificationIcon } from './NotificationIcon';
+import { NotificationDescription } from './NotificationDescription';
const ThrottleDelayMs = 1000;
const useStyles = makeStyles(theme => ({
- description: {
- maxHeight: '5rem',
- overflow: 'auto',
- },
severityItem: {
alignContent: 'center',
},
@@ -53,8 +50,10 @@ const useStyles = makeStyles(theme => ({
verticalAlign: 'text-bottom',
},
notificationInfoRow: {
- marginLeft: theme.spacing(0.5),
marginRight: theme.spacing(0.5),
+ '&:not(:first-child)': {
+ marginLeft: theme.spacing(0.5),
+ },
},
}));
@@ -240,9 +239,9 @@ export const NotificationsTable = ({
)}
{notification.payload.description ? (
-
- {notification.payload.description}
-
+
) : null}
@@ -318,7 +317,6 @@ export const NotificationsTable = ({
onMarkAllRead,
onNotificationsSelectChange,
classes.severityItem,
- classes.description,
classes.broadcastIcon,
classes.notificationInfoRow,
markAsReadOnLinkOpen,
diff --git a/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx b/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx
index 9bcc497132..f6d6ff48c9 100644
--- a/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx
+++ b/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx
@@ -16,6 +16,7 @@
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import { makeStyles } from '@material-ui/core/styles';
+import Tooltip from '@material-ui/core/Tooltip';
const useStyles = makeStyles({
label: {
@@ -23,6 +24,7 @@ const useStyles = makeStyles({
maxWidth: '2rem',
'& span': {
paddingRight: '0px',
+ marginRight: '2px',
},
},
});
@@ -43,13 +45,15 @@ export const SelectAll = ({
label={count > 0 ? `(${count})` : undefined}
className={classes.label}
control={
- 0}
- indeterminate={count > 0 && totalCount !== count}
- onChange={onSelectAll}
- />
+
+ 0}
+ indeterminate={count > 0 && totalCount !== count}
+ onChange={onSelectAll}
+ />
+
}
/>
);
diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md
index 5cfb6f1a18..ffe7af52f1 100644
--- a/plugins/org-react/CHANGELOG.md
+++ b/plugins/org-react/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-org-react
+## 0.1.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
## 0.1.45-next.0
### Patch Changes
diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json
index a077894bc7..99403d689d 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.45-next.0",
+ "version": "0.1.45",
"backstage": {
"role": "web-library",
"pluginId": "org",
diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md
index 3bc994dcfe..90292a0f78 100644
--- a/plugins/org/CHANGELOG.md
+++ b/plugins/org/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-org
+## 0.6.47
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+
+## 0.6.47-next.1
+
+### Patch Changes
+
+- f3f84f1: Minor extension type updates after frontend API bump
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/plugin-catalog-common@1.1.7
+
## 0.6.47-next.0
### Patch Changes
diff --git a/plugins/org/package.json b/plugins/org/package.json
index 09e801d7be..a48dad9f25 100644
--- a/plugins/org/package.json
+++ b/plugins/org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-org",
- "version": "0.6.47-next.0",
+ "version": "0.6.47",
"description": "A Backstage plugin that helps you create entity pages for your organization",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md
index ec8e371159..a9709fd88b 100644
--- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md
+++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-permission-backend-module-allow-all-policy
+## 0.2.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-permission-common@0.9.3
+
## 0.2.15-next.0
### Patch Changes
diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json
index 1d4c5af43c..5cddb60c98 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.2.15-next.0",
+ "version": "0.2.15",
"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 4b63ed405c..196de3238b 100644
--- a/plugins/permission-backend/CHANGELOG.md
+++ b/plugins/permission-backend/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-permission-backend
+## 0.7.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.7.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.3
+
## 0.7.7-next.0
### Patch Changes
diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json
index 5e599c2d3b..f3c840b693 100644
--- a/plugins/permission-backend/package.json
+++ b/plugins/permission-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-permission-backend",
- "version": "0.7.7-next.0",
+ "version": "0.7.7",
"backstage": {
"role": "backend-plugin",
"pluginId": "permission",
diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts
index ece99f08c7..822d10b858 100644
--- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts
+++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts
@@ -148,9 +148,7 @@ describe('PermissionIntegrationClient', () => {
],
);
- expect(response).toEqual(
- expect.objectContaining([{ id: '123', result: AuthorizeResult.ALLOW }]),
- );
+ expect(response).toEqual([{ id: '123', result: AuthorizeResult.ALLOW }]);
});
it('should not include authorization headers if no token is supplied', async () => {
diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md
index 4594d38882..6c9fe8a91b 100644
--- a/plugins/permission-node/CHANGELOG.md
+++ b/plugins/permission-node/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-permission-node
+## 0.10.7
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.10.7-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.9.3
+
## 0.10.7-next.0
### Patch Changes
diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json
index 3384aa7524..1ae3935b4c 100644
--- a/plugins/permission-node/package.json
+++ b/plugins/permission-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-permission-node",
- "version": "0.10.7-next.0",
+ "version": "0.10.7",
"description": "Common permission and authorization utilities for backend plugins",
"backstage": {
"role": "node-library",
diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md
index cf0248c840..9a377d5dc8 100644
--- a/plugins/permission-react/CHANGELOG.md
+++ b/plugins/permission-react/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-permission-react
+## 0.4.39
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.12.1
+
## 0.4.39-next.0
### Patch Changes
diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json
index 098959f342..17b4d65b51 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.39-next.0",
+ "version": "0.4.39",
"backstage": {
"role": "web-library",
"pluginId": "permission",
diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md
index 8bb9e73f52..7541c8faf1 100644
--- a/plugins/proxy-backend/CHANGELOG.md
+++ b/plugins/proxy-backend/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-proxy-backend
+## 0.6.9
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-proxy-node@0.1.11
+
+## 0.6.9-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-proxy-node@0.1.11-next.1
+
## 0.6.9-next.0
### Patch Changes
diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json
index 331ea354c2..c75e9a42e7 100644
--- a/plugins/proxy-backend/package.json
+++ b/plugins/proxy-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-proxy-backend",
- "version": "0.6.9-next.0",
+ "version": "0.6.9",
"description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md
index 12abd91157..e2bf6a15cf 100644
--- a/plugins/proxy-node/CHANGELOG.md
+++ b/plugins/proxy-node/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-proxy-node
+## 0.1.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+
+## 0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+
## 0.1.11-next.0
### Patch Changes
diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json
index 4ac4d1fe96..9134a6c2b5 100644
--- a/plugins/proxy-node/package.json
+++ b/plugins/proxy-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-proxy-node",
- "version": "0.1.11-next.0",
+ "version": "0.1.11",
"description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend",
"backstage": {
"role": "node-library",
diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md
index f1984afffd..6061d57a41 100644
--- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-backend-module-azure
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json
index ff47d56ed1..d76d5c41d8 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.2.16-next.0",
+ "version": "0.2.16",
"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 3238b13b21..d18fbaf5d4 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,50 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket-cloud
+## 0.3.0
+
+### Minor Changes
+
+- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues.
+
+ **BREAKING CHANGES**
+
+ - **@backstage/integration** (`src/bitbucketCloud/core.ts`)
+
+ - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited.
+
+ - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`)
+ - `getBitbucketClient` now returns a `Promise` and **must** be awaited.
+ - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited.
+
+ **OAuth usage example**
+
+ ```yaml
+ integrations:
+ bitbucketCloud:
+ - clientId: client-id
+ clientSecret: client-secret
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json
index 7c44716291..512e5a7c55 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.2.16-next.0",
+ "version": "0.3.0",
"description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
index 722bf01303..a278410639 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
@@ -25,6 +25,7 @@ import {
import { Config } from '@backstage/config';
import { getAuthorizationHeader } from './helpers';
+import { getBitbucketCloudOAuthToken } from '@backstage/integration';
import { examples } from './bitbucketCloud.examples';
const createRepository = async (opts: {
@@ -216,7 +217,7 @@ export function createPublishBitbucketCloudAction(options: {
);
}
- const authorization = getAuthorizationHeader(
+ const authorization = await getAuthorizationHeader(
ctx.input.token ? { token: ctx.input.token } : integrationConfig.config,
);
@@ -249,6 +250,18 @@ export function createPublishBitbucketCloudAction(options: {
username: 'x-token-auth',
password: ctx.input.token,
};
+ } else if (
+ integrationConfig.config.clientId &&
+ integrationConfig.config.clientSecret
+ ) {
+ const token = await getBitbucketCloudOAuthToken(
+ integrationConfig.config.clientId,
+ integrationConfig.config.clientSecret,
+ );
+ auth = {
+ username: 'x-token-auth',
+ password: token,
+ };
} else {
if (
!integrationConfig.config.username ||
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts
index 55ee430091..2ac54894d5 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts
@@ -22,11 +22,11 @@ jest.mock('bitbucket', () => ({
}));
describe('bitbucketCloud:branchRestriction:create', () => {
- it('getBitbucketClient should return the correct headers with username and password', () => {
+ it('getBitbucketClient should return the correct headers with username and password', async () => {
expect.assertions(1);
const username = 'username';
const password = 'password';
- getBitbucketClient({ username: username, appPassword: password });
+ await getBitbucketClient({ username: username, appPassword: password });
expect(Bitbucket).toHaveBeenCalledWith({
auth: {
username: username,
@@ -35,12 +35,14 @@ describe('bitbucketCloud:branchRestriction:create', () => {
});
});
- it('getBitbucketClient should throw if only one of username or password is provided', () => {
+ it('getBitbucketClient should throw if only one of username or password is provided', async () => {
expect.assertions(2);
const username = 'username';
const password = 'password';
- expect(() => getBitbucketClient({ username })).toThrow(Error);
- expect(() => getBitbucketClient({ appPassword: password })).toThrow(Error);
+ await expect(getBitbucketClient({ username })).rejects.toThrow(Error);
+ await expect(getBitbucketClient({ appPassword: password })).rejects.toThrow(
+ Error,
+ );
});
});
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts
index 233818e00a..a12f63f980 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts
@@ -37,6 +37,8 @@ const createBitbucketCloudBranchRestriction = async (opts: {
token?: string;
username?: string;
appPassword?: string;
+ clientId?: string;
+ clientSecret?: string;
};
}) => {
const {
@@ -52,7 +54,7 @@ const createBitbucketCloudBranchRestriction = async (opts: {
authorization,
} = opts;
- const bitbucket = getBitbucketClient(authorization);
+ const bitbucket = await getBitbucketClient(authorization);
return await bitbucket.branchrestrictions.create({
_body: {
groups: groups,
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
index 4bf9226db7..372f45c928 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
@@ -68,7 +68,7 @@ export const createBitbucketPipelinesRunAction = (options: {
const host = 'bitbucket.org';
const integrationConfig = integrations.bitbucketCloud.byHost(host);
- const authorization = getAuthorizationHeader(
+ const authorization = await getAuthorizationHeader(
token ? { token } : integrationConfig!.config,
);
let response: Response;
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts
index 9969f13eb2..40dd09d386 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts
@@ -28,6 +28,7 @@ import {
import { Config } from '@backstage/config';
import fs from 'fs-extra';
import { getAuthorizationHeader } from './helpers';
+import { getBitbucketCloudOAuthToken } from '@backstage/integration';
import { examples } from './bitbucketCloudPullRequest.examples';
const createPullRequest = async (opts: {
@@ -317,7 +318,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
);
}
- const authorization = getAuthorizationHeader(
+ const authorization = await getAuthorizationHeader(
ctx.input.token ? { token: ctx.input.token } : integrationConfig.config,
);
@@ -365,6 +366,18 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
username: 'x-token-auth',
password: ctx.input.token,
};
+ } else if (
+ integrationConfig.config.clientId &&
+ integrationConfig.config.clientSecret
+ ) {
+ const token = await getBitbucketCloudOAuthToken(
+ integrationConfig.config.clientId,
+ integrationConfig.config.clientSecret,
+ );
+ auth = {
+ username: 'x-token-auth',
+ password: token,
+ };
} else {
if (
!integrationConfig.config.username ||
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts
index 2e68d4e8b8..47a212fcab 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts
@@ -15,12 +15,28 @@
*/
import { Bitbucket } from 'bitbucket';
+import { getBitbucketCloudOAuthToken } from '@backstage/integration';
-export const getBitbucketClient = (config: {
+export const getBitbucketClient = async (config: {
token?: string;
username?: string;
appPassword?: string;
+ clientId?: string;
+ clientSecret?: string;
}) => {
+ // If OAuth credentials provided, fetch token
+ if (config.clientId && config.clientSecret) {
+ const token = await getBitbucketCloudOAuthToken(
+ config.clientId,
+ config.clientSecret,
+ );
+ return new Bitbucket({
+ auth: {
+ token,
+ },
+ });
+ }
+
if (config.token) {
return new Bitbucket({
auth: {
@@ -38,15 +54,26 @@ export const getBitbucketClient = (config: {
});
}
throw new Error(
- `Authorization has not been provided for Bitbucket Cloud. Please add either provide a username and token or username and appPassword to the Integrations config`,
+ `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`,
);
};
-export const getAuthorizationHeader = (config: {
+export const getAuthorizationHeader = async (config: {
username?: string;
appPassword?: string;
token?: string;
-}) => {
+ clientId?: string;
+ clientSecret?: string;
+}): Promise => {
+ // OAuth authentication
+ if (config.clientId && config.clientSecret) {
+ const token = await getBitbucketCloudOAuthToken(
+ config.clientId,
+ config.clientSecret,
+ );
+ return `Bearer ${token}`;
+ }
+
// TODO: appPassword can be removed once fully
// deprecated by BitBucket on 9th June 2026.
if (config.username && (config.token ?? config.appPassword)) {
@@ -62,6 +89,6 @@ export const getAuthorizationHeader = (config: {
}
throw new Error(
- `Authorization has not been provided for Bitbucket Cloud. Please add either provide a username and token or username and appPassword to the Integrations config`,
+ `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`,
);
};
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
index d533bb63f4..1591b4f1df 100644
--- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket-server
+## 0.2.16
+
+### Patch Changes
+
+- 5a6aca2: Improve error message when provided target branch is missing
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- 5a6aca2: Improve error message when provided target branch is missing
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json
index 3590b4112a..98544c3baa 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.2.16-next.0",
+ "version": "0.2.16",
"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 56c67b9a14..404670d688 100644
--- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket
+## 0.3.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.3.17-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json
index 4516920aa7..cbbab54143 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.3.17-next.0",
+ "version": "0.3.17",
"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 db8919e652..1644c76641 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-backend-module-confluence-to-markdown
+## 0.3.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.3.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
index ada64e8f9c..177f7ea512 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.3.16-next.0",
+ "version": "0.3.16",
"description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
index 4f600fc7b0..89042a3074 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-scaffolder-backend-module-cookiecutter
+## 0.3.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.3.18-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json
index 64fb9236af..82e2713623 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.3.18-next.0",
+ "version": "0.3.19-next.0",
"description": "A module for the scaffolder backend that lets you template projects using cookiecutter",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
index ab185367bc..5294344dc7 100644
--- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-backend-module-gcp
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json
index c9a954831e..bb50ebd277 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.2.16-next.0",
+ "version": "0.2.16",
"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 26696b6452..cc45967b3b 100644
--- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-backend-module-gerrit
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json
index 07592568ca..62d4ebb286 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.2.16-next.0",
+ "version": "0.2.16",
"description": "The gerrit module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
index d3a913d054..46852ad119 100644
--- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-backend-module-gitea
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json
index 99e2c05f86..21e7d4c98a 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.2.16-next.0",
+ "version": "0.2.16",
"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 46ec5e751d..0a636abb22 100644
--- a/plugins/scaffolder-backend-module-github/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-scaffolder-backend-module-github
+## 0.9.3
+
+### Patch Changes
+
+- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.9.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.9.3-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json
index f22f9a3741..4f58dc4eee 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.9.3-next.0",
+ "version": "0.9.3",
"description": "The github module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts
index 9b9aaff9c2..8076a4de01 100644
--- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts
+++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts
@@ -31,6 +31,9 @@ const mockOctokit = {
listForAuthenticatedUser: jest.fn(),
listBranches: jest.fn(),
},
+ orgs: {
+ listForAuthenticatedUser: jest.fn(),
+ },
},
};
jest.mock('octokit', () => ({
@@ -95,6 +98,30 @@ describe('handleAutocompleteRequest', () => {
});
});
+ it('should return owners', async () => {
+ const handleAutocompleteRequest = createHandleAutocompleteRequest({
+ integrations: mockIntegrations,
+ });
+
+ mockOctokit.rest.orgs.listForAuthenticatedUser.mockResolvedValue({
+ data: [
+ {
+ login: 'backstage',
+ },
+ ],
+ });
+
+ const result = await handleAutocompleteRequest({
+ resource: 'owners',
+ token: 'token',
+ context: {},
+ });
+
+ expect(result).toEqual({
+ results: [{ id: 'backstage' }],
+ });
+ });
+
it('should throw an error for invalid resource', async () => {
const handleAutocompleteRequest = createHandleAutocompleteRequest({
integrations: mockIntegrations,
diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts
index 35d6b4f930..b3efde9333 100644
--- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts
+++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts
@@ -64,6 +64,15 @@ export function createHandleAutocompleteRequest(options: {
return { results };
}
+ case 'owners': {
+ const orgs = await client.paginate(
+ client.rest.orgs.listForAuthenticatedUser,
+ );
+
+ const results = orgs.map(r => ({ id: r.login }));
+
+ return { results };
+ }
default:
throw new InputError(`Invalid resource: ${resource}`);
}
diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
index a22e1d71bf..41a8c18bd9 100644
--- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/plugin-scaffolder-backend-module-gitlab
+## 0.11.0
+
+### Minor Changes
+
+- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.11.0-next.1
+
+### Minor Changes
+
+- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.10.1-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json
index 21786474e2..a447da9417 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.10.1-next.0",
+ "version": "0.11.0",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
index 584f2bcc7d..c7100f71f9 100644
--- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-scaffolder-backend-module-notifications
+## 0.1.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-notifications-node@0.2.22
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.1.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-notifications-common@0.2.0
+ - @backstage/plugin-notifications-node@0.2.22-next.1
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.1.17-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json
index 64bd96badb..89b76a7808 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.1.17-next.0",
+ "version": "0.1.17",
"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 e6e17558b3..48db86578e 100644
--- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-scaffolder-backend-module-rails
+## 0.5.16
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.5.16-next.1
+
+### Patch Changes
+
+- 2bae83a: Bumped dev dependencies `@types/node`
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.5.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json
index 2ae26dd016..0a778daeff 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.5.16-next.0",
+ "version": "0.5.16",
"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-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
index 8fbec8e18b..427c5bcfd0 100644
--- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-scaffolder-backend-module-sentry
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.2.16-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.2.16-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json
index a012921907..d8f8c2dd31 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.2.16-next.0",
+ "version": "0.2.16",
"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 2c436729f9..dba988783c 100644
--- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-scaffolder-backend-module-yeoman
+## 0.4.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.7-next.0
+
+## 0.4.17
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.6
+
+## 0.4.17-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+ - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1
+
## 0.4.17-next.0
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json
index 66e1342a8e..afacdad9a4 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.4.17-next.0",
+ "version": "0.4.18-next.0",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md
index e9b0bdc5e7..17776a4b49 100644
--- a/plugins/scaffolder-backend/CHANGELOG.md
+++ b/plugins/scaffolder-backend/CHANGELOG.md
@@ -1,5 +1,107 @@
# @backstage/plugin-scaffolder-backend
+## 3.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-openapi-utils@0.6.5-next.0
+ - @backstage/backend-defaults@0.14.1-next.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/integration@1.19.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 3.1.0
+
+### Minor Changes
+
+- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity.
+
+### Patch Changes
+
+- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Updated `isolated-vm` to `6.0.1`
+- 25b560e: Internal change to support new versions of the `logform` library
+- 8f4aded: Fixing OpenAPI definition
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0
+ - @backstage/integration@1.19.0
+ - @backstage/plugin-auth-node@0.6.10
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5
+ - @backstage/backend-defaults@0.14.0
+ - @backstage/backend-openapi-utils@0.6.4
+ - @backstage/plugin-events-node@0.4.18
+ - @backstage/plugin-permission-node@0.10.7
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15
+ - @backstage/plugin-catalog-node@1.20.1
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 3.1.0-next.1
+
+### Patch Changes
+
+- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0
+- 2bae83a: Updated `isolated-vm` to `6.0.1`
+- 25b560e: Internal change to support new versions of the `logform` library
+- 1226647: Updated dependency `esbuild` to `^0.27.0`.
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1
+ - @backstage/backend-defaults@0.14.0-next.1
+ - @backstage/backend-openapi-utils@0.6.4-next.1
+ - @backstage/plugin-auth-node@0.6.10-next.1
+ - @backstage/plugin-events-node@0.4.18-next.1
+ - @backstage/plugin-permission-node@0.10.7-next.1
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/config@1.3.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1
+ - @backstage/plugin-catalog-node@1.20.1-next.1
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 3.1.0-next.0
### Minor Changes
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index c3876a7926..7d26366e6d 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend",
- "version": "3.1.0-next.0",
+ "version": "3.1.1-next.0",
"description": "The Backstage backend plugin that helps you create new things",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml
index 0a7c841d8f..c8f85974ea 100644
--- a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml
+++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml
@@ -69,7 +69,7 @@ spec:
url: https://github.com/backstage/community/tree/main/backstage-community-sessions
- id: publish
name: Publish
- action: publish:bitbucket
+ action: publish:bitbucketCloud
input:
description: This is ${{ parameters.name }}
repoUrl: ${{ parameters.repoUrl }}
@@ -81,10 +81,28 @@ spec:
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
+ - id: create-pr-file
+ name: Create PR File
+ action: fetch:plain
+ input:
+ targetPath: ./pr-demo
+ url: https://github.com/backstage/community/blob/main/README.md
+ - id: create-pr
+ name: Create Pull Request
+ action: publish:bitbucketCloud:pull-request
+ input:
+ repoUrl: ${{ parameters.repoUrl }}
+ title: Test PR for ${{ parameters.name }}
+ description: This is a test pull request created by the scaffolder template to test OAuth support
+ sourceBranch: scaffolder-pr-${{ parameters.name }}
+ targetBranch: master
+
output:
links:
- title: Repository
url: ${{ steps['publish'].output.remoteUrl }}
+ - title: Pull Request
+ url: ${{ steps['create-pr'].output.pullRequestUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts
index 3a15b345db..ef704bc9e0 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts
@@ -134,6 +134,7 @@ export class TaskWorker {
additionalTemplateFilters,
additionalTemplateGlobals,
permissions,
+ config,
});
return new TaskWorker({
diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md
index 1bc44a09c5..5ca131e060 100644
--- a/plugins/scaffolder-common/CHANGELOG.md
+++ b/plugins/scaffolder-common/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-scaffolder-common
+## 1.7.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+
## 1.7.4-next.0
### Patch Changes
diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json
index 1949f12e18..24898228dc 100644
--- a/plugins/scaffolder-common/package.json
+++ b/plugins/scaffolder-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-common",
- "version": "1.7.4-next.0",
+ "version": "1.7.4",
"description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin",
"backstage": {
"role": "common-library",
diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md
index 91fff5040d..7980a7146e 100644
--- a/plugins/scaffolder-node-test-utils/CHANGELOG.md
+++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-scaffolder-node-test-utils
+## 0.3.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/backend-test-utils@1.10.3-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.10.2
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-node@0.12.2
+
+## 0.3.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.10.2-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/types@1.2.2
+ - @backstage/plugin-scaffolder-node@0.12.2-next.1
+
## 0.3.6-next.0
### Patch Changes
diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json
index 3c79f30575..f7b3a4179e 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.3.6-next.0",
+ "version": "0.3.7-next.0",
"backstage": {
"role": "node-library",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md
index a621739d50..dcb7d78184 100644
--- a/plugins/scaffolder-node/CHANGELOG.md
+++ b/plugins/scaffolder-node/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-scaffolder-node
+## 0.12.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/backend-plugin-api@1.6.0
+ - @backstage/plugin-scaffolder-common@1.7.4
+
+## 0.12.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/backend-plugin-api@1.6.0-next.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.2
+ - @backstage/plugin-permission-common@0.9.3
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+
## 0.12.2-next.0
### Patch Changes
diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json
index 995ae74b4b..ec82ec8ac5 100644
--- a/plugins/scaffolder-node/package.json
+++ b/plugins/scaffolder-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-node",
- "version": "0.12.2-next.0",
+ "version": "0.12.2",
"description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "node-library",
diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md
index 552035d603..363804954b 100644
--- a/plugins/scaffolder-react/CHANGELOG.md
+++ b/plugins/scaffolder-react/CHANGELOG.md
@@ -1,5 +1,48 @@
# @backstage/plugin-scaffolder-react
+## 1.19.4
+
+### Patch Changes
+
+- fb029b6: Updated luxon types
+- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- 9b38f22: Updated dependency `use-immer` to `^0.11.0`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/theme@0.7.1
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder-common@1.7.4
+
+## 1.19.4-next.2
+
+### Patch Changes
+
+- fb029b6: Updated luxon types
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- 9b38f22: Updated dependency `use-immer` to `^0.11.0`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/theme@0.7.1-next.0
+ - @backstage/types@1.2.2
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+
## 1.19.4-next.1
### Patch Changes
diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json
index 81a01f9229..3bad0ad4c7 100644
--- a/plugins/scaffolder-react/package.json
+++ b/plugins/scaffolder-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-react",
- "version": "1.19.4-next.1",
+ "version": "1.19.4",
"description": "A frontend library that helps other Backstage plugins interact with the Scaffolder",
"backstage": {
"role": "web-library",
diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
index b0746e0672..6a542396de 100644
--- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
+++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
@@ -514,11 +514,12 @@ describe('Stepper', () => {
const mockFormData = { firstName: 'John' };
- Object.defineProperty(window, 'location', {
- value: {
- search: `?formData=${JSON.stringify(mockFormData)}`,
- },
- });
+ // Use history.replaceState to set the query string (jsdom 27 doesn't allow redefining window.location)
+ window.history.replaceState(
+ {},
+ '',
+ `?formData=${JSON.stringify(mockFormData)}`,
+ );
const { getByRole } = await renderInTestApp(
diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md
index 69554368b7..14a27c8d2a 100644
--- a/plugins/scaffolder/CHANGELOG.md
+++ b/plugins/scaffolder/CHANGELOG.md
@@ -1,5 +1,56 @@
# @backstage/plugin-scaffolder
+## 1.35.0
+
+### Minor Changes
+
+- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners.
+
+### Patch Changes
+
+- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system.
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/integration@1.19.0
+ - @backstage/frontend-plugin-api@0.13.2
+ - @backstage/plugin-scaffolder-react@1.19.4
+ - @backstage/core-components@0.18.4
+ - @backstage/plugin-catalog-react@1.21.4
+ - @backstage/core-plugin-api@1.12.1
+ - @backstage/integration-react@1.2.13
+ - @backstage/plugin-permission-react@0.4.39
+ - @backstage/plugin-scaffolder-common@1.7.4
+ - @backstage/plugin-techdocs-react@1.3.6
+
+## 1.34.4-next.1
+
+### Patch Changes
+
+- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`.
+ Updated dependency `@rjsf/core` to `5.24.13`.
+ Updated dependency `@rjsf/material-ui` to `5.24.13`.
+ Updated dependency `@rjsf/validator-ajv8` to `5.24.13`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.13.2-next.1
+ - @backstage/plugin-scaffolder-react@1.19.4-next.2
+ - @backstage/integration@1.18.3-next.1
+ - @backstage/plugin-catalog-react@1.21.4-next.2
+ - @backstage/core-components@0.18.4-next.2
+ - @backstage/catalog-client@1.12.1
+ - @backstage/catalog-model@1.7.6
+ - @backstage/core-plugin-api@1.12.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.13-next.0
+ - @backstage/types@1.2.2
+ - @backstage/plugin-catalog-common@1.1.7
+ - @backstage/plugin-permission-react@0.4.39-next.0
+ - @backstage/plugin-scaffolder-common@1.7.4-next.0
+ - @backstage/plugin-techdocs-common@0.1.1
+ - @backstage/plugin-techdocs-react@1.3.6-next.0
+
## 1.34.4-next.0
### Patch Changes
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index 55302da959..2d6f52eab4 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder",
- "version": "1.34.4-next.0",
+ "version": "1.35.0",
"description": "The Backstage plugin that helps you create new things",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md
index 7c0247cae9..7ffe367327 100644
--- a/plugins/scaffolder/report-alpha.api.md
+++ b/plugins/scaffolder/report-alpha.api.md
@@ -350,6 +350,21 @@ const _default: OverridableFrontendPlugin<
field: () => Promise;
};
}>;
+ 'scaffolder-form-field:scaffolder/repo-owner-picker': OverridableExtensionDefinition<{
+ kind: 'scaffolder-form-field';
+ name: 'repo-owner-picker';
+ config: {};
+ configInput: {};
+ output: ExtensionDataRef<
+ () => Promise,
+ 'scaffolder.form-field-loader',
+ {}
+ >;
+ inputs: {};
+ params: {
+ field: () => Promise;
+ };
+ }>;
'scaffolder-form-field:scaffolder/repo-url-picker': OverridableExtensionDefinition<{
kind: 'scaffolder-form-field';
name: 'repo-url-picker';
@@ -499,6 +514,8 @@ export const scaffolderTranslationRef: TranslationRef<
readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available';
readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository';
readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository';
+ readonly 'fields.repoOwnerPicker.title': 'Owner';
+ readonly 'fields.repoOwnerPicker.description': 'The owner of the repository';
readonly 'aboutCard.launchTemplate': 'Launch Template';
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.';
diff --git a/plugins/scaffolder/report.api.md b/plugins/scaffolder/report.api.md
index 2e9c6be1ef..e624b157fa 100644
--- a/plugins/scaffolder/report.api.md
+++ b/plugins/scaffolder/report.api.md
@@ -391,6 +391,30 @@ export const RepoBranchPickerFieldExtension: FieldExtensionComponent_2<
}
>;
+// @public
+export const RepoOwnerPickerFieldExtension: FieldExtensionComponent_2<
+ string,
+ {
+ host?: string | undefined;
+ requestUserCredentials?:
+ | {
+ secretsKey: string;
+ additionalScopes?:
+ | {
+ azure?: string[] | undefined;
+ github?: string[] | undefined;
+ gitlab?: string[] | undefined;
+ bitbucket?: string[] | undefined;
+ gerrit?: string[] | undefined;
+ gitea?: string[] | undefined;
+ }
+ | undefined;
+ }
+ | undefined;
+ excludedOwners?: string[] | undefined;
+ }
+>;
+
// @public
export const repoPickerValidation: (
value: string,
diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx
index 427f679188..86400fcfde 100644
--- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx
+++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx
@@ -20,14 +20,6 @@ import { MockFileSystemAccess } from '../../../lib/filesystem/MockFileSystemAcce
import { DirectoryEditorProvider } from './DirectoryEditorContext';
import { TemplateEditorBrowser } from './TemplateEditorBrowser';
-Blob.prototype.text = async function text() {
- return new Promise(resolve => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result as string);
- reader.readAsText(this);
- });
-};
-
describe('TemplateEditorBrowser', () => {
it('should render files and expand dirs without exploding', async () => {
await renderInTestApp(
diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx
index 429e6d493a..5215fc0968 100644
--- a/plugins/scaffolder/src/alpha/extensions.tsx
+++ b/plugins/scaffolder/src/alpha/extensions.tsx
@@ -127,6 +127,14 @@ export const repoBranchPickerFormField = FormFieldBlueprint.make({
},
});
+export const repoOwnerPickerFormField = FormFieldBlueprint.make({
+ name: 'repo-owner-picker',
+ params: {
+ field: () =>
+ import('./fields/RepoOwnerPicker').then(m => m.RepoOwnerPicker),
+ },
+});
+
export const scaffolderApi = ApiBlueprint.make({
params: defineParams =>
defineParams({
diff --git a/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts
new file mode 100644
index 0000000000..059d71fb83
--- /dev/null
+++ b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createFormField } from '@backstage/plugin-scaffolder-react/alpha';
+import {
+ RepoOwnerPicker as Component,
+ RepoOwnerPickerFieldSchema,
+} from '../../components/fields/RepoOwnerPicker';
+
+export const RepoOwnerPicker = createFormField({
+ component: Component,
+ name: 'RepoOwnerPicker',
+ schema: RepoOwnerPickerFieldSchema,
+});
diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx
index 5bb744e2ea..502aab0bbc 100644
--- a/plugins/scaffolder/src/alpha/plugin.tsx
+++ b/plugins/scaffolder/src/alpha/plugin.tsx
@@ -35,6 +35,7 @@ import {
ownedEntityPickerFormField,
ownerPickerFormField,
repoBranchPickerFormField,
+ repoOwnerPickerFormField,
repoUrlPickerFormField,
scaffolderApi,
scaffolderNavItem,
@@ -88,5 +89,6 @@ export default createFrontendPlugin({
myGroupsPickerFormField,
ownedEntityPickerFormField,
repoBranchPickerFormField,
+ repoOwnerPickerFormField,
],
});
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx
new file mode 100644
index 0000000000..2e335a44a3
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { fireEvent, screen } from '@testing-library/react';
+import { renderInTestApp } from '@backstage/test-utils';
+
+import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker';
+
+describe('DefaultRepoOwnerPicker', () => {
+ it('renders an input field', async () => {
+ const { getByRole } = await renderInTestApp(
+ ,
+ );
+
+ expect(getByRole('textbox')).toBeInTheDocument();
+ expect(getByRole('textbox')).toHaveValue('owner1');
+ });
+
+ it('input field disabled', async () => {
+ await renderInTestApp(
+ ,
+ );
+
+ const input = screen.getByRole('textbox');
+
+ // Expect input to be disabled
+ expect(input).toBeDisabled();
+ expect(input).toHaveValue('owner1');
+ });
+
+ it('calls onChange when the input field changes', async () => {
+ const onChange = jest.fn();
+
+ const { getByRole } = await renderInTestApp(
+ ,
+ );
+
+ const input = getByRole('textbox');
+
+ fireEvent.change(input, {
+ target: { value: 'owner2' },
+ });
+
+ expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' });
+ });
+});
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx
new file mode 100644
index 0000000000..48d7da8ba8
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import FormControl from '@material-ui/core/FormControl';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import TextField from '@material-ui/core/TextField';
+import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
+
+import { BaseRepoOwnerPickerProps } from './types';
+import { scaffolderTranslationRef } from '../../../translation';
+
+/**
+ * The underlying component that is rendered in the form for the `DefaultRepoOwnerPicker`
+ * field extension.
+ *
+ * @public
+ *
+ */
+export const DefaultRepoOwnerPicker = ({
+ onChange,
+ state,
+ rawErrors,
+ isDisabled,
+ required,
+ schema,
+}: BaseRepoOwnerPickerProps) => {
+ const { owner } = state;
+
+ const { t } = useTranslationRef(scaffolderTranslationRef);
+
+ return (
+ 0 && !owner}
+ >
+ onChange({ owner: e.target.value })}
+ value={owner}
+ />
+
+ {schema?.description ?? t('fields.repoOwnerPicker.description')}
+
+
+ );
+};
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx
new file mode 100644
index 0000000000..bfc6c76768
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ScaffolderApi,
+ scaffolderApiRef,
+} from '@backstage/plugin-scaffolder-react';
+import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker';
+import { act, fireEvent, waitFor, screen } from '@testing-library/react';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
+import userEvent from '@testing-library/user-event';
+
+describe('GitHubRepoOwnerPicker', () => {
+ const scaffolderApiMock: Partial = {
+ autocomplete: jest
+ .fn()
+ .mockResolvedValue({ results: [{ id: 'owner1' }, { id: 'owner2' }] }),
+ };
+
+ it('renders an input field', async () => {
+ const { getByRole } = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(getByRole('textbox')).toBeInTheDocument();
+ expect(getByRole('textbox')).toHaveValue('owner1');
+ });
+
+ it('input field disabled', async () => {
+ await renderInTestApp(
+
+
+ ,
+ );
+
+ const input = screen.getByRole('textbox');
+
+ // Expect input to be disabled
+ expect(input).toBeDisabled();
+ expect(input).toHaveValue('owner1');
+ });
+
+ it('calls onChange when the input field changes', async () => {
+ const onChange = jest.fn();
+
+ const { getByRole } = await renderInTestApp(
+
+
+ ,
+ );
+
+ const input = getByRole('textbox');
+
+ act(() => {
+ input.focus();
+ fireEvent.change(input, {
+ target: { value: 'owner2' },
+ });
+ input.blur();
+ });
+
+ expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' });
+ });
+
+ it('should populate owners', async () => {
+ const onChange = jest.fn();
+
+ const { getByRole, getByText } = await renderInTestApp(
+
+
+ ,
+ );
+
+ // Open the Autocomplete dropdown
+ const input = getByRole('textbox');
+ await userEvent.click(input);
+
+ // Verify that the available owners are shown
+ await waitFor(() => expect(getByText('owner1')).toBeInTheDocument());
+
+ // Verify that selecting an option calls onChange
+ await userEvent.click(getByText('owner1'));
+ expect(onChange).toHaveBeenCalledWith({
+ owner: 'owner1',
+ });
+ });
+
+ it('should filter out excluded owners', async () => {
+ const onChange = jest.fn();
+
+ const { getByRole, getByText } = await renderInTestApp(
+
+
+ ,
+ );
+
+ // Open the Autocomplete dropdown
+ const input = getByRole('textbox');
+ await userEvent.click(input);
+
+ // Verify that the excluded owners are not shown
+ await waitFor(() => expect(getByText('owner2')).toBeInTheDocument());
+ expect(screen.queryByText('owner1')).not.toBeInTheDocument();
+ });
+});
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx
new file mode 100644
index 0000000000..9258ef312d
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useApi } from '@backstage/core-plugin-api';
+import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
+import FormControl from '@material-ui/core/FormControl';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import TextField from '@material-ui/core/TextField';
+import Autocomplete from '@material-ui/lab/Autocomplete';
+import { useCallback, useState } from 'react';
+import useDebounce from 'react-use/esm/useDebounce';
+import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
+
+import { BaseRepoOwnerPickerProps } from './types';
+import { scaffolderTranslationRef } from '../../../translation';
+
+/**
+ * The underlying component that is rendered in the form for the `GitHubRepoOwnerPicker`
+ * field extension.
+ *
+ * @public
+ *
+ */
+export const GitHubRepoOwnerPicker = ({
+ onChange,
+ state,
+ rawErrors,
+ accessToken,
+ isDisabled,
+ required,
+ schema,
+ excludedOwners = [],
+}: BaseRepoOwnerPickerProps<{
+ accessToken?: string;
+ excludedOwners?: string[];
+}>) => {
+ const { host, owner } = state;
+
+ const [availableOwners, setAvailableOwners] = useState([]);
+
+ const scaffolderApi = useApi(scaffolderApiRef);
+ const { t } = useTranslationRef(scaffolderTranslationRef);
+
+ const updateAvailableOwners = useCallback(() => {
+ if (!scaffolderApi.autocomplete || !accessToken || !host) {
+ setAvailableOwners([]);
+ return;
+ }
+
+ scaffolderApi
+ .autocomplete({
+ token: accessToken,
+ resource: 'owners',
+ context: { host },
+ provider: 'github',
+ })
+ .then(({ results }) => {
+ const owners = results
+ .map(r => r.id)
+ .filter(id => !excludedOwners.includes(id));
+
+ setAvailableOwners(owners);
+ })
+ .catch(() => {
+ setAvailableOwners([]);
+ });
+ }, [host, accessToken, scaffolderApi, excludedOwners]);
+
+ useDebounce(updateAvailableOwners, 500, [updateAvailableOwners]);
+
+ return (
+ 0 && !owner}
+ >
+ {
+ onChange({ owner: newValue || '' });
+ }}
+ disabled={isDisabled}
+ options={availableOwners}
+ renderInput={params => (
+
+ )}
+ freeSolo
+ autoSelect
+ />
+
+ {schema?.description ?? t('fields.repoOwnerPicker.description')}
+
+
+ );
+};
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx
new file mode 100644
index 0000000000..c51d74416b
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Form } from '@backstage/plugin-scaffolder-react/alpha';
+import validator from '@rjsf/validator-ajv8';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
+import {
+ scmIntegrationsApiRef,
+ ScmIntegrationsApi,
+ scmAuthApiRef,
+ ScmAuthApi,
+} from '@backstage/integration-react';
+
+import {
+ SecretsContextProvider,
+ scaffolderApiRef,
+ useTemplateSecrets,
+ ScaffolderRJSFField,
+} from '@backstage/plugin-scaffolder-react';
+import { act, fireEvent, screen } from '@testing-library/react';
+import { RepoOwnerPicker } from './RepoOwnerPicker';
+
+describe('RepoOwnerPicker', () => {
+ const mockIntegrationsApi: Partial = {
+ byHost: () => ({ type: 'github' }),
+ };
+
+ let mockScmAuthApi: Partial;
+
+ beforeEach(() => {
+ mockScmAuthApi = {
+ getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }),
+ };
+ });
+
+ describe('happy path rendering', () => {
+ it('should render the repo owner picker with minimal props', async () => {
+ const onSubmit = jest.fn();
+
+ const { getByRole, getByText } = await renderInTestApp(
+
+
+
+ ,
+ );
+
+ const input = getByRole('textbox');
+ const submitButton = getByRole('button');
+
+ fireEvent.change(input, { target: { value: 'owner1' } });
+
+ fireEvent.click(submitButton);
+
+ expect(onSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ formData: 'owner1',
+ }),
+ expect.anything(),
+ );
+
+ expect(getByText('Owner')).toBeInTheDocument();
+ expect(getByText('The owner of the repository')).toBeInTheDocument();
+ });
+
+ it('should disable the picker when ui:disabled', async () => {
+ const onSubmit = jest.fn();
+
+ await renderInTestApp(
+
+
+
+ ,
+ );
+
+ const input = screen.getByRole('textbox');
+
+ expect(input).toBeDisabled();
+ });
+
+ it('should render properly with title and description', async () => {
+ const { getByText } = await renderInTestApp(
+
+
+
+ ,
+ );
+
+ expect(getByText('test title')).toBeInTheDocument();
+ expect(getByText('test description')).toBeInTheDocument();
+ });
+ });
+
+ describe('requestUserCredentials', () => {
+ it('should call the scmAuthApi with the correct params', async () => {
+ const secretsKey = 'testKey';
+
+ const SecretsComponent = () => {
+ const { secrets } = useTemplateSecrets();
+ const secret = secrets[secretsKey];
+ return secret ?
{secret}
: null;
+ };
+
+ const { getByText } = await renderInTestApp(
+
+
+
+ ,
+ );
+
+ await act(async () => {
+ // need to wait for the debounce to finish
+ await new Promise(resolve => setTimeout(resolve, 600));
+ });
+
+ expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
+ url: 'https://github.com',
+ additionalScope: {
+ repoWrite: true,
+ customScopes: {
+ github: ['workflow'],
+ },
+ },
+ });
+
+ expect(getByText('abc123')).toBeInTheDocument();
+ });
+
+ it('should call the scmAuthApi with the correct params if workspace is nested', async () => {
+ await renderInTestApp(
+
+
+
+ ,
+ );
+
+ await act(async () => {
+ // need to wait for the debounce to finish
+ await new Promise(resolve => setTimeout(resolve, 600));
+ });
+
+ expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
+ url: 'https://github.com',
+ additionalScope: {
+ repoWrite: true,
+ },
+ });
+ });
+
+ it('should not call the scmAuthApi if secret is available in the state', async () => {
+ const secretsKey = 'testKey';
+
+ const SecretsComponent = () => {
+ const { secrets } = useTemplateSecrets();
+ const secret = secrets[secretsKey];
+ return secret ?
{secret}
: null;
+ };
+
+ const { getByText } = await renderInTestApp(
+
+
+
+ ,
+ );
+
+ await act(async () => {
+ // need to wait for the debounce to finish
+ await new Promise(resolve => setTimeout(resolve, 600));
+ });
+
+ // as we already have a secret in the state, getCredentials should not be called again.
+ expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0);
+
+ expect(getByText('abc123')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx
new file mode 100644
index 0000000000..bcb1a0001c
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useApi } from '@backstage/core-plugin-api';
+import {
+ scmIntegrationsApiRef,
+ scmAuthApiRef,
+} from '@backstage/integration-react';
+import { useEffect, useState, useCallback, useMemo } from 'react';
+import useDebounce from 'react-use/esm/useDebounce';
+import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react';
+
+import { RepoOwnerPickerProps } from './schema';
+import { RepoOwnerPickerState } from './types';
+import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker';
+import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker';
+
+/**
+ * The underlying component that is rendered in the form for the `RepoOwnerPicker`
+ * field extension.
+ *
+ * @public
+ */
+export const RepoOwnerPicker = (props: RepoOwnerPickerProps) => {
+ const { uiSchema, onChange, rawErrors, formData, schema, required } = props;
+ const [state, setState] = useState({
+ owner: formData || '',
+ });
+ const excludedOwners = useMemo(
+ () => uiSchema?.['ui:options']?.excludedOwners ?? [],
+ [uiSchema],
+ );
+ const { host, owner } = state;
+
+ const integrationApi = useApi(scmIntegrationsApiRef);
+ const scmAuthApi = useApi(scmAuthApiRef);
+
+ const { secrets, setSecrets } = useTemplateSecrets();
+
+ useDebounce(
+ async () => {
+ const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {};
+
+ if (!requestUserCredentials || !host) {
+ return;
+ }
+
+ // don't show login prompt if secret value is already in state
+ if (secrets[requestUserCredentials.secretsKey]) {
+ return;
+ }
+
+ // user has requested that we use the users credentials
+ // so lets grab them using the scmAuthApi and pass through
+ // any additional scopes from the ui:options
+ const { token } = await scmAuthApi.getCredentials({
+ url: `https://${host}`,
+ additionalScope: {
+ repoWrite: true,
+ customScopes: requestUserCredentials.additionalScopes,
+ },
+ });
+
+ // set the secret using the key provided in the ui:options for use
+ // in the templating the manifest with ${{ secrets[secretsKey] }}
+ setSecrets({ [requestUserCredentials.secretsKey]: token });
+ },
+ 500,
+ [host, uiSchema],
+ );
+
+ useEffect(() => {
+ if (uiSchema?.['ui:options']?.host) {
+ const hostUiOption = uiSchema['ui:options'].host;
+ setState(prevState => ({ ...prevState, host: hostUiOption }));
+ }
+ }, [uiSchema]);
+
+ useEffect(() => {
+ onChange(owner);
+ }, [owner, onChange]);
+
+ const updateLocalState = useCallback(
+ (newState: RepoOwnerPickerState) => {
+ setState(prevState => ({ ...prevState, ...newState }));
+ },
+ [setState],
+ );
+
+ const hostType = (host && integrationApi.byHost(host)?.type) ?? null;
+
+ const renderRepoOwnerPicker = () => {
+ switch (hostType) {
+ case 'github':
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ };
+
+ return renderRepoOwnerPicker();
+};
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts
new file mode 100644
index 0000000000..d93e5e22e5
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 { RepoOwnerPicker } from './RepoOwnerPicker';
+export {
+ RepoOwnerPickerFieldSchema,
+ type RepoOwnerPickerUiOptions,
+ RepoOwnerPickerSchema,
+} from './schema';
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts
new file mode 100644
index 0000000000..7931ca1eec
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { makeFieldSchema } from '@backstage/plugin-scaffolder-react';
+
+export const RepoOwnerPickerFieldSchema = makeFieldSchema({
+ output: z => z.string(),
+ uiOptions: z =>
+ z.object({
+ host: z.string().optional(),
+ excludedOwners: z
+ .array(z.string())
+ .optional()
+ .describe('List of excluded owners'),
+ requestUserCredentials: z
+ .object({
+ secretsKey: z
+ .string()
+ .describe(
+ 'Key used within the template secrets context to store the credential',
+ ),
+ additionalScopes: z
+ .object({
+ gitea: z
+ .array(z.string())
+ .optional()
+ .describe('Additional Gitea scopes to request'),
+ gerrit: z
+ .array(z.string())
+ .optional()
+ .describe('Additional Gerrit scopes to request'),
+ github: z
+ .array(z.string())
+ .optional()
+ .describe('Additional GitHub scopes to request'),
+ gitlab: z
+ .array(z.string())
+ .optional()
+ .describe('Additional GitLab scopes to request'),
+ bitbucket: z
+ .array(z.string())
+ .optional()
+ .describe('Additional BitBucket scopes to request'),
+ azure: z
+ .array(z.string())
+ .optional()
+ .describe('Additional Azure scopes to request'),
+ })
+ .optional()
+ .describe('Additional permission scopes to request'),
+ })
+ .optional()
+ .describe(
+ 'If defined will request user credentials to auth against the given SCM platform',
+ ),
+ }),
+});
+
+/**
+ * The input props that can be specified under `ui:options` for the
+ * `RepoOwnerPicker` field extension.
+ */
+export type RepoOwnerPickerUiOptions = NonNullable<
+ (typeof RepoOwnerPickerFieldSchema.TProps.uiSchema)['ui:options']
+>;
+
+export type RepoOwnerPickerProps = typeof RepoOwnerPickerFieldSchema.TProps;
+
+// This has been duplicated from /plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts
+// NOTE: There is a bug with this failing validation in the custom field explorer due
+// to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if
+// requestUserCredentials is not defined
+export const RepoOwnerPickerSchema = RepoOwnerPickerFieldSchema.schema;
diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts
new file mode 100644
index 0000000000..eceed58df5
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { RJSFSchema } from '@rjsf/utils';
+
+export interface RepoOwnerPickerState {
+ host?: string;
+ owner?: string;
+}
+
+export type BaseRepoOwnerPickerProps = T & {
+ onChange: (state: RepoOwnerPickerState) => void;
+ state: RepoOwnerPickerState;
+ rawErrors: string[];
+ isDisabled?: boolean;
+ required?: boolean;
+ schema?: RJSFSchema;
+};
diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx
index 59f311fe88..8a058d5320 100644
--- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx
+++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx
@@ -33,7 +33,7 @@ describe('', () => {
aria-expanded="true"
class="MuiAutocomplete-root MuiAutocomplete-hasClearIcon MuiAutocomplete-hasPopupIcon"
role="combobox"
- style="position: relative; height: 18px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
+ style="position: relative; height: 18px; width: 100%; overflow: auto; -webkit-overflow-scrolling: touch; will-change: transform; direction: ltr;"
>
', () => {