diff --git a/.changeset/afraid-items-drum.md b/.changeset/afraid-items-drum.md new file mode 100644 index 0000000000..94ddd608cd --- /dev/null +++ b/.changeset/afraid-items-drum.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..b41ec091a3 --- /dev/null +++ b/.changeset/alert-api-replay.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..377782c2f9 --- /dev/null +++ b/.changeset/all-socks-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. diff --git a/.changeset/better-hats-cross.md b/.changeset/better-hats-cross.md deleted file mode 100644 index 99444b7fbf..0000000000 --- a/.changeset/better-hats-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Adjusted the log line wording of task worker starting diff --git a/.changeset/better-steaks-act.md b/.changeset/better-steaks-act.md deleted file mode 100644 index b3ce601245..0000000000 --- a/.changeset/better-steaks-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The templates executed with the `yarn new` command now supports templating filenames. diff --git a/.changeset/bumpy-planets-go.md b/.changeset/bumpy-planets-go.md new file mode 100644 index 0000000000..5477711c1a --- /dev/null +++ b/.changeset/bumpy-planets-go.md @@ -0,0 +1,7 @@ +--- +'@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 new file mode 100644 index 0000000000..33937a1286 --- /dev/null +++ b/.changeset/chatty-sides-wear.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..894999cf56 --- /dev/null +++ b/.changeset/chilly-bikes-rule.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..e9b1ff66ba --- /dev/null +++ b/.changeset/chilly-hotels-walk.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..2e78f9245e --- /dev/null +++ b/.changeset/chilly-waves-relate.md @@ -0,0 +1,11 @@ +--- +'@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 new file mode 100644 index 0000000000..66002903ab --- /dev/null +++ b/.changeset/clean-toys-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': patch +--- + +remove leading dot in auth cookie cleanup call diff --git a/.changeset/common-coins-stare.md b/.changeset/common-coins-stare.md new file mode 100644 index 0000000000..0bd0078cf9 --- /dev/null +++ b/.changeset/common-coins-stare.md @@ -0,0 +1,7 @@ +--- +'@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 new file mode 100644 index 0000000000..4db99675ac --- /dev/null +++ b/.changeset/crazy-hornets-spend.md @@ -0,0 +1,11 @@ +--- +'@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-1764689798.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1764689798.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/dependabot-0d8f6de.md b/.changeset/dependabot-0d8f6de.md new file mode 100644 index 0000000000..b0a3955f2d --- /dev/null +++ b/.changeset/dependabot-0d8f6de.md @@ -0,0 +1,47 @@ +--- +'@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 new file mode 100644 index 0000000000..93563cffc6 --- /dev/null +++ b/.changeset/dependabot-5e654fe.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +build(deps): bump `node-forge` from 1.3.1 to 1.3.2 diff --git a/.changeset/early-doors-visit.md b/.changeset/early-doors-visit.md new file mode 100644 index 0000000000..41cd9e92b8 --- /dev/null +++ b/.changeset/early-doors-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Allow configuration of the `referrerPolicy` diff --git a/.changeset/error-boundary-api.md b/.changeset/error-boundary-api.md new file mode 100644 index 0000000000..74803f1dc0 --- /dev/null +++ b/.changeset/error-boundary-api.md @@ -0,0 +1,7 @@ +--- +'@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-ants-count.md b/.changeset/every-ants-count.md deleted file mode 100644 index 963c3a521a..0000000000 --- a/.changeset/every-ants-count.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-test-utils': minor ---- - -Added support for Postgres 18 to the available `TestDatabases`. - -Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. diff --git a/.changeset/every-clocks-arrive.md b/.changeset/every-clocks-arrive.md deleted file mode 100644 index d2061913e3..0000000000 --- a/.changeset/every-clocks-arrive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': patch ---- - -Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. diff --git a/.changeset/famous-jars-lose.md b/.changeset/famous-jars-lose.md new file mode 100644 index 0000000000..4070701e4e --- /dev/null +++ b/.changeset/famous-jars-lose.md @@ -0,0 +1,7 @@ +--- +'@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 new file mode 100644 index 0000000000..0e3425a505 --- /dev/null +++ b/.changeset/fifty-coats-feel.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..423ef89721 --- /dev/null +++ b/.changeset/fifty-lights-marry.md @@ -0,0 +1,9 @@ +--- +'@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 new file mode 100644 index 0000000000..0af6d0dfc0 --- /dev/null +++ b/.changeset/fine-eagles-sleep.md @@ -0,0 +1,7 @@ +--- +'@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/fine-hands-return.md b/.changeset/fine-hands-return.md deleted file mode 100644 index d3bf69c9b9..0000000000 --- a/.changeset/fine-hands-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix default text color in Backstage UI diff --git a/.changeset/five-seas-jam.md b/.changeset/five-seas-jam.md deleted file mode 100644 index 4aa481422f..0000000000 --- a/.changeset/five-seas-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Updated `AWS SES` client to version 2 to support `nodemailer` version 7. diff --git a/.changeset/flat-pillows-rush.md b/.changeset/flat-pillows-rush.md new file mode 100644 index 0000000000..bb64013f62 --- /dev/null +++ b/.changeset/flat-pillows-rush.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..d698e1482c --- /dev/null +++ b/.changeset/floppy-bobcats-serve.md @@ -0,0 +1,12 @@ +--- +'@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 new file mode 100644 index 0000000000..81b35589c1 --- /dev/null +++ b/.changeset/four-peaches-train.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..a816946bcc --- /dev/null +++ b/.changeset/fruity-rivers-arrive.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..0f02acee2b --- /dev/null +++ b/.changeset/fruity-words-melt.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..d0d7a59551 --- /dev/null +++ b/.changeset/full-needles-drive.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..2af6702295 --- /dev/null +++ b/.changeset/funny-hornets-peel.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..95f52966ec --- /dev/null +++ b/.changeset/funny-papayas-rest.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..61725d1f1d --- /dev/null +++ b/.changeset/fuzzy-phones-own.md @@ -0,0 +1,9 @@ +--- +'@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 new file mode 100644 index 0000000000..fa2c08a671 --- /dev/null +++ b/.changeset/fuzzy-rivers-travel.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..84cec56fab --- /dev/null +++ b/.changeset/fuzzy-trees-live.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..ed4372ee06 --- /dev/null +++ b/.changeset/gentle-singers-love.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..e8b1397180 --- /dev/null +++ b/.changeset/gentle-trains-juggle.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..271a8e8955 --- /dev/null +++ b/.changeset/great-files-shave.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..640699e001 --- /dev/null +++ b/.changeset/green-lizards-boil.md @@ -0,0 +1,5 @@ +--- +'@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/grumpy-planes-bet.md b/.changeset/grumpy-planes-bet.md deleted file mode 100644 index bf7d36be41..0000000000 --- a/.changeset/grumpy-planes-bet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Fixed a bug in the Scaffolder's template parsing in the `useTemplateSchema` hook by removing the title instead of setting it to `undefined` diff --git a/.changeset/happy-bottles-invite.md b/.changeset/happy-bottles-invite.md new file mode 100644 index 0000000000..07fc222cad --- /dev/null +++ b/.changeset/happy-bottles-invite.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..46f4d798ed --- /dev/null +++ b/.changeset/happy-streets-dress.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..4c0cd5f47e --- /dev/null +++ b/.changeset/honest-bears-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Switched `textextensions` dependency for `text-extensions`. diff --git a/.changeset/kind-hoops-double.md b/.changeset/kind-hoops-double.md new file mode 100644 index 0000000000..fd2f9f023f --- /dev/null +++ b/.changeset/kind-hoops-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Made the github urls case insensitive. diff --git a/.changeset/large-planes-punch.md b/.changeset/large-planes-punch.md new file mode 100644 index 0000000000..3216653eb9 --- /dev/null +++ b/.changeset/large-planes-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Fix for `jose` types diff --git a/.changeset/legal-cloths-spend.md b/.changeset/legal-cloths-spend.md new file mode 100644 index 0000000000..50d965307a --- /dev/null +++ b/.changeset/legal-cloths-spend.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..2003492e29 --- /dev/null +++ b/.changeset/legal-otters-punch.md @@ -0,0 +1,7 @@ +--- +'@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 new file mode 100644 index 0000000000..b41c09c928 --- /dev/null +++ b/.changeset/lemon-corners-hug.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..b8ec201f1c --- /dev/null +++ b/.changeset/loose-pets-slide.md @@ -0,0 +1,10 @@ +--- +'@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-carpets-throw.md b/.changeset/loud-carpets-throw.md deleted file mode 100644 index 169703d815..0000000000 --- a/.changeset/loud-carpets-throw.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added a template for the `yarn new` command to create an catalog entity provider. To add this template to an explicit list in the root `package.json`, use `@backstage/cli/templates/catalog-provider-module`. diff --git a/.changeset/loud-yaks-watch.md b/.changeset/loud-yaks-watch.md new file mode 100644 index 0000000000..b7ad65b9eb --- /dev/null +++ b/.changeset/loud-yaks-watch.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..4d76caa098 --- /dev/null +++ b/.changeset/lucky-days-hug.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes': patch +--- + +add missing i18n support for kubernetes and kubernetes-react diff --git a/.changeset/many-planes-join.md b/.changeset/many-planes-join.md new file mode 100644 index 0000000000..66107cd370 --- /dev/null +++ b/.changeset/many-planes-join.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..9a5ab24e0e --- /dev/null +++ b/.changeset/metal-boxes-laugh.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..f96d33a779 --- /dev/null +++ b/.changeset/metal-humans-lose.md @@ -0,0 +1,21 @@ +--- +'@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 new file mode 100644 index 0000000000..9fd7645570 --- /dev/null +++ b/.changeset/modern-taxes-start.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..aff67a2a10 --- /dev/null +++ b/.changeset/neat-pens-clean.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..c6119702b4 --- /dev/null +++ b/.changeset/nice-humans-cry.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..b58c03200f --- /dev/null +++ b/.changeset/nice-trams-shake.md @@ -0,0 +1,5 @@ +--- +'@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/ninety-cobras-feel.md b/.changeset/ninety-cobras-feel.md deleted file mode 100644 index e287ef33ed..0000000000 --- a/.changeset/ninety-cobras-feel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. diff --git a/.changeset/old-cats-shake.md b/.changeset/old-cats-shake.md new file mode 100644 index 0000000000..3ca30a0421 --- /dev/null +++ b/.changeset/old-cats-shake.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..f089c7da21 --- /dev/null +++ b/.changeset/old-parks-smell.md @@ -0,0 +1,20 @@ +--- +'@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 new file mode 100644 index 0000000000..b58bf2b3d5 --- /dev/null +++ b/.changeset/open-points-beam.md @@ -0,0 +1,37 @@ +--- +'@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-seas-divide.md b/.changeset/polite-seas-divide.md deleted file mode 100644 index e265e6df90..0000000000 --- a/.changeset/polite-seas-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix the default font size in Backstage UI. diff --git a/.changeset/pre.json b/.changeset/pre.json index 67e48bb29c..f44b869497 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,230 +2,256 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.114", - "@backstage/app-defaults": "1.7.1", - "example-app-next": "0.0.28", - "app-next-example-plugin": "0.0.28", - "example-backend": "0.0.43", - "@backstage/backend-app-api": "1.2.8", - "@backstage/backend-defaults": "0.13.0", + "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.5", - "@backstage/backend-openapi-utils": "0.6.2", - "@backstage/backend-plugin-api": "1.4.4", - "@backstage/backend-test-utils": "1.9.1", - "@backstage/catalog-client": "1.12.0", - "@backstage/catalog-model": "1.7.5", - "@backstage/cli": "0.34.4", + "@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", + "@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.14", + "@backstage/cli-node": "0.2.15", "@backstage/codemods": "0.1.52", - "@backstage/config": "1.3.5", - "@backstage/config-loader": "1.10.5", - "@backstage/core-app-api": "1.19.1", - "@backstage/core-compat-api": "0.5.3", - "@backstage/core-components": "0.18.2", - "@backstage/core-plugin-api": "1.11.1", - "@backstage/create-app": "0.7.5", - "@backstage/dev-utils": "1.1.15", - "e2e-test": "0.2.33", + "@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/e2e-test-utils": "0.1.1", "@backstage/errors": "1.2.7", - "@backstage/eslint-plugin": "0.1.12", - "@backstage/frontend-app-api": "0.13.1", - "@backstage/frontend-defaults": "0.3.2", - "@backstage/frontend-dynamic-feature-loader": "0.1.6", - "@internal/frontend": "0.0.14", - "@backstage/frontend-plugin-api": "0.12.1", - "@backstage/frontend-test-utils": "0.4.0", - "@backstage/integration": "1.18.1", - "@backstage/integration-aws-node": "0.1.18", - "@backstage/integration-react": "1.2.11", + "@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/integration-aws-node": "0.1.19", + "@backstage/integration-react": "1.2.12", "@internal/opaque": "0.0.1", "@backstage/release-manifests": "0.0.13", - "@backstage/repo-tools": "0.15.3", - "@internal/scaffolder": "0.0.14", - "@techdocs/cli": "1.10.0", - "techdocs-cli-embedded-app": "0.2.113", - "@backstage/test-utils": "1.7.12", + "@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/types": "1.2.2", - "@backstage/ui": "0.8.0", + "@backstage/ui": "0.9.0", "@backstage/version-bridge": "1.0.11", "yarn-plugin-backstage": "0.0.7", - "@backstage/plugin-api-docs": "0.13.0", + "@backstage/plugin-api-docs": "0.13.1", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10", - "@backstage/plugin-app": "0.3.1", - "@backstage/plugin-app-backend": "0.5.7", - "@backstage/plugin-app-node": "0.1.38", - "@backstage/plugin-app-visualizer": "0.1.24", - "@backstage/plugin-auth": "0.1.1", - "@backstage/plugin-auth-backend": "0.25.5", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-github-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-google-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-guest-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.13", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.8", - "@backstage/plugin-auth-backend-module-okta-provider": "0.2.8", - "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.1", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.8", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.8", - "@backstage/plugin-auth-node": "0.6.8", - "@backstage/plugin-auth-react": "0.1.20", - "@backstage/plugin-bitbucket-cloud-common": "0.3.3", - "@backstage/plugin-catalog": "1.31.4", - "@backstage/plugin-catalog-backend": "3.1.2", - "@backstage/plugin-catalog-backend-module-aws": "0.4.16", - "@backstage/plugin-catalog-backend-module-azure": "0.3.10", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.7", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.4", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.4", - "@backstage/plugin-catalog-backend-module-gcp": "0.3.13", - "@backstage/plugin-catalog-backend-module-gerrit": "0.3.7", - "@backstage/plugin-catalog-backend-module-gitea": "0.1.5", - "@backstage/plugin-catalog-backend-module-github": "0.11.1", - "@backstage/plugin-catalog-backend-module-github-org": "0.3.15", - "@backstage/plugin-catalog-backend-module-gitlab": "0.7.4", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.14", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.5", - "@backstage/plugin-catalog-backend-module-ldap": "0.11.10", - "@backstage/plugin-catalog-backend-module-logs": "0.1.15", - "@backstage/plugin-catalog-backend-module-msgraph": "0.8.1", - "@backstage/plugin-catalog-backend-module-openapi": "0.2.15", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.15", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.13", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.5", - "@backstage/plugin-catalog-common": "1.1.6", - "@backstage/plugin-catalog-graph": "0.5.2", - "@backstage/plugin-catalog-import": "0.13.6", - "@backstage/plugin-catalog-node": "1.19.1", - "@backstage/plugin-catalog-react": "1.21.2", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.22", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.10", - "@backstage/plugin-config-schema": "0.1.73", - "@backstage/plugin-devtools": "0.1.32", - "@backstage/plugin-devtools-backend": "0.5.10", - "@backstage/plugin-devtools-common": "0.1.18", - "@backstage/plugin-events-backend": "0.5.7", - "@backstage/plugin-events-backend-module-aws-sqs": "0.4.16", - "@backstage/plugin-events-backend-module-azure": "0.2.25", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.25", - "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.6", - "@backstage/plugin-events-backend-module-gerrit": "0.2.25", - "@backstage/plugin-events-backend-module-github": "0.4.5", - "@backstage/plugin-events-backend-module-gitlab": "0.3.6", - "@backstage/plugin-events-backend-module-google-pubsub": "0.1.5", - "@backstage/plugin-events-backend-module-kafka": "0.1.4", - "@backstage/plugin-events-backend-test-utils": "0.1.49", - "@backstage/plugin-events-node": "0.4.16", - "@internal/plugin-todo-list": "1.0.44", - "@internal/plugin-todo-list-backend": "1.0.44", - "@internal/plugin-todo-list-common": "1.0.27", - "@backstage/plugin-gateway-backend": "1.0.6", - "@backstage/plugin-home": "0.8.13", - "@backstage/plugin-home-react": "0.1.31", - "@backstage/plugin-kubernetes": "0.12.12", - "@backstage/plugin-kubernetes-backend": "0.20.3", - "@backstage/plugin-kubernetes-cluster": "0.0.30", - "@backstage/plugin-kubernetes-common": "0.9.7", - "@backstage/plugin-kubernetes-node": "0.3.5", - "@backstage/plugin-kubernetes-react": "0.5.12", - "@backstage/plugin-mcp-actions-backend": "0.1.4", - "@backstage/plugin-mui-to-bui": "0.2.0", - "@backstage/plugin-notifications": "0.5.10", - "@backstage/plugin-notifications-backend": "0.5.11", - "@backstage/plugin-notifications-backend-module-email": "0.3.14", - "@backstage/plugin-notifications-backend-module-slack": "0.2.0", - "@backstage/plugin-notifications-common": "0.1.1", - "@backstage/plugin-notifications-node": "0.2.20", - "@backstage/plugin-org": "0.6.45", - "@backstage/plugin-org-react": "0.1.43", - "@backstage/plugin-permission-backend": "0.7.5", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.13", - "@backstage/plugin-permission-common": "0.9.2", - "@backstage/plugin-permission-node": "0.10.5", - "@backstage/plugin-permission-react": "0.4.37", - "@backstage/plugin-proxy-backend": "0.6.7", - "@backstage/plugin-proxy-node": "0.1.9", - "@backstage/plugin-scaffolder": "1.34.2", - "@backstage/plugin-scaffolder-backend": "3.0.0", - "@backstage/plugin-scaffolder-backend-module-azure": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.15", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.14", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.16", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-github": "0.9.1", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.9.6", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.15", - "@backstage/plugin-scaffolder-backend-module-rails": "0.5.14", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.14", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.15", - "@backstage/plugin-scaffolder-common": "1.7.2", - "@backstage/plugin-scaffolder-node": "0.12.0", - "@backstage/plugin-scaffolder-node-test-utils": "0.3.4", - "@backstage/plugin-scaffolder-react": "1.19.2", - "@backstage/plugin-search": "1.4.31", - "@backstage/plugin-search-backend": "2.0.7", - "@backstage/plugin-search-backend-module-catalog": "0.3.9", - "@backstage/plugin-search-backend-module-elasticsearch": "1.7.7", - "@backstage/plugin-search-backend-module-explore": "0.3.8", - "@backstage/plugin-search-backend-module-pg": "0.5.49", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.14", - "@backstage/plugin-search-backend-module-techdocs": "0.4.7", - "@backstage/plugin-search-backend-node": "1.3.16", - "@backstage/plugin-search-common": "1.2.20", - "@backstage/plugin-search-react": "1.9.5", - "@backstage/plugin-signals": "0.0.24", - "@backstage/plugin-signals-backend": "0.3.9", - "@backstage/plugin-signals-node": "0.1.25", - "@backstage/plugin-signals-react": "0.0.16", - "@backstage/plugin-techdocs": "1.15.1", - "@backstage/plugin-techdocs-addons-test-utils": "1.1.1", - "@backstage/plugin-techdocs-backend": "2.1.1", + "@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-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", + "@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-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-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-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-techdocs-common": "0.1.1", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.29", - "@backstage/plugin-techdocs-node": "1.13.8", - "@backstage/plugin-techdocs-react": "1.3.4", - "@backstage/plugin-user-settings": "0.8.27", - "@backstage/plugin-user-settings-backend": "0.3.7", + "@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-user-settings-common": "0.0.1" }, "changesets": [ - "better-hats-cross", - "better-steaks-act", - "every-ants-count", - "every-clocks-arrive", - "fine-hands-return", - "five-seas-jam", - "grumpy-planes-bet", - "loud-carpets-throw", - "ninety-cobras-feel", - "polite-seas-divide", - "rich-streets-rule", - "short-sides-feel", - "silver-garlics-thank", - "solid-bees-agree", - "solid-dancers-march", - "stupid-doodles-love", - "tender-regions-know", - "typescript-constructor-refactor", - "warm-moments-repeat", - "wild-owls-divide" + "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" ] } diff --git a/.changeset/quiet-hats-sleep.md b/.changeset/quiet-hats-sleep.md new file mode 100644 index 0000000000..5f75f22f6c --- /dev/null +++ b/.changeset/quiet-hats-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fixing OpenAPI definition diff --git a/.changeset/rare-rice-throw.md b/.changeset/rare-rice-throw.md new file mode 100644 index 0000000000..6fa0434f67 --- /dev/null +++ b/.changeset/rare-rice-throw.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..c28ade8de9 --- /dev/null +++ b/.changeset/ready-results-march.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..9ccb2dd94b --- /dev/null +++ b/.changeset/renovate-7b76d6d.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..d2370a1c8b --- /dev/null +++ b/.changeset/renovate-959c095.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Updated dependency `@rjsf/utils` to `5.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 new file mode 100644 index 0000000000..9b3692bc1c --- /dev/null +++ b/.changeset/renovate-97eef4b.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Updated dependency `typescript-json-schema` to `^0.67.0`. diff --git a/.changeset/renovate-a3c2cbd.md b/.changeset/renovate-a3c2cbd.md new file mode 100644 index 0000000000..2fde85d39e --- /dev/null +++ b/.changeset/renovate-a3c2cbd.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Updated dependency `use-immer` to `^0.11.0`. diff --git a/.changeset/renovate-ad7bf17.md b/.changeset/renovate-ad7bf17.md new file mode 100644 index 0000000000..a0f90ff97d --- /dev/null +++ b/.changeset/renovate-ad7bf17.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Updated dependency `@types/archiver` to `^7.0.0`. diff --git a/.changeset/renovate-d7ceb06.md b/.changeset/renovate-d7ceb06.md new file mode 100644 index 0000000000..d22f266301 --- /dev/null +++ b/.changeset/renovate-d7ceb06.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..958e032b64 --- /dev/null +++ b/.changeset/renovate-ea25c93.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `webpack` to `~5.103.0`. diff --git a/.changeset/rich-streets-rule.md b/.changeset/rich-streets-rule.md deleted file mode 100644 index 07f431b6a7..0000000000 --- a/.changeset/rich-streets-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. diff --git a/.changeset/rotten-melons-sleep.md b/.changeset/rotten-melons-sleep.md new file mode 100644 index 0000000000..c12e0a9669 --- /dev/null +++ b/.changeset/rotten-melons-sleep.md @@ -0,0 +1,15 @@ +--- +'@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 new file mode 100644 index 0000000000..56600db540 --- /dev/null +++ b/.changeset/salty-camels-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed internal usage of `yargs`. diff --git a/.changeset/seven-games-rest.md b/.changeset/seven-games-rest.md new file mode 100644 index 0000000000..b498c3b138 --- /dev/null +++ b/.changeset/seven-games-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Corrected `ErrorCallback` type to work with Node 22 types diff --git a/.changeset/short-groups-knock.md b/.changeset/short-groups-knock.md new file mode 100644 index 0000000000..0b622a71ed --- /dev/null +++ b/.changeset/short-groups-knock.md @@ -0,0 +1,7 @@ +--- +'@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/short-sides-feel.md b/.changeset/short-sides-feel.md deleted file mode 100644 index a7e3542ba1..0000000000 --- a/.changeset/short-sides-feel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. diff --git a/.changeset/silver-garlics-thank.md b/.changeset/silver-garlics-thank.md deleted file mode 100644 index d95781eb04..0000000000 --- a/.changeset/silver-garlics-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix font smoothing as default in Backstage UI. diff --git a/.changeset/slick-books-sleep.md b/.changeset/slick-books-sleep.md new file mode 100644 index 0000000000..7aa3be5b03 --- /dev/null +++ b/.changeset/slick-books-sleep.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..4627d02e81 --- /dev/null +++ b/.changeset/slick-onions-wash.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..497d86761b --- /dev/null +++ b/.changeset/slimy-islands-play.md @@ -0,0 +1,7 @@ +--- +'@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 new file mode 100644 index 0000000000..5773db6853 --- /dev/null +++ b/.changeset/slimy-mugs-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Fix PostgreSQL 18 `TestDatabases` by pinning the data directory diff --git a/.changeset/solid-bees-agree.md b/.changeset/solid-bees-agree.md deleted file mode 100644 index 8feecaf6d4..0000000000 --- a/.changeset/solid-bees-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/eslint-plugin': minor ---- - -Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. diff --git a/.changeset/solid-dancers-march.md b/.changeset/solid-dancers-march.md deleted file mode 100644 index 5633bb3e2e..0000000000 --- a/.changeset/solid-dancers-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Revert `storageApiRef` implementation diff --git a/.changeset/sour-bats-press.md b/.changeset/sour-bats-press.md new file mode 100644 index 0000000000..bfcf3985e6 --- /dev/null +++ b/.changeset/sour-bats-press.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..03cc8a5440 --- /dev/null +++ b/.changeset/spicy-teeth-study.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..fe640ccd44 --- /dev/null +++ b/.changeset/stale-eagles-rush.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..5cf0f0f72a --- /dev/null +++ b/.changeset/stupid-cases-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Switched ECMAScript version to ES2023. diff --git a/.changeset/stupid-doodles-love.md b/.changeset/stupid-doodles-love.md deleted file mode 100644 index 878ad853cd..0000000000 --- a/.changeset/stupid-doodles-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@techdocs/cli': patch ---- - -Fix for missing styles due to move to BUI. diff --git a/.changeset/tall-ideas-lead.md b/.changeset/tall-ideas-lead.md new file mode 100644 index 0000000000..60bef903d8 --- /dev/null +++ b/.changeset/tall-ideas-lead.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..f317185ea2 --- /dev/null +++ b/.changeset/tame-mirrors-sit.md @@ -0,0 +1,5 @@ +--- +'@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 new file mode 100644 index 0000000000..e20ec43ae0 --- /dev/null +++ b/.changeset/tender-dancers-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated to use new utilities from `@backstage/cli-common`. diff --git a/.changeset/tender-regions-know.md b/.changeset/tender-regions-know.md deleted file mode 100644 index 8f59567597..0000000000 --- a/.changeset/tender-regions-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth': patch ---- - -Authentication content screen now uses application title. diff --git a/.changeset/tired-dogs-remain.md b/.changeset/tired-dogs-remain.md new file mode 100644 index 0000000000..06c193068b --- /dev/null +++ b/.changeset/tired-dogs-remain.md @@ -0,0 +1,12 @@ +--- +'@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 new file mode 100644 index 0000000000..dab952df65 --- /dev/null +++ b/.changeset/tough-lies-grow.md @@ -0,0 +1,6 @@ +--- +'@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 new file mode 100644 index 0000000000..359eda66f3 --- /dev/null +++ b/.changeset/twenty-ducks-relate.md @@ -0,0 +1,7 @@ +--- +'@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/typescript-constructor-refactor.md b/.changeset/typescript-constructor-refactor.md deleted file mode 100644 index b24cb5dbd0..0000000000 --- a/.changeset/typescript-constructor-refactor.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-kubernetes-react': patch -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-notifications-node': patch -'@backstage/plugin-permission-react': patch -'@backstage/plugin-scaffolder-backend-module-gcp': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-search-backend-module-elasticsearch': patch -'@backstage/plugin-search-backend-module-pg': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-react': patch -'@backstage/plugin-signals': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-events-backend': patch -'@backstage/plugin-catalog-backend-module-backstage-openapi': patch -'@backstage/plugin-events-node': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-notifications-backend-module-email': patch -'@backstage/plugin-events-backend-module-aws-sqs': patch -'@backstage/plugin-devtools-backend': patch -'@backstage/plugin-catalog-backend-module-unprocessed': patch -'@backstage/plugin-catalog-backend-module-azure': patch -'@backstage/plugin-catalog-backend-module-aws': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-techdocs-node': patch -'@backstage/plugin-catalog-unprocessed-entities': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-backend-module-ldap': patch -'@backstage/plugin-bitbucket-cloud-common': patch -'@backstage/plugin-auth-node': patch -'@backstage/backend-app-api': patch -'@backstage/backend-defaults': patch -'@backstage/config-loader': patch -'@backstage/core-app-api': patch -'@backstage/core-plugin-api': patch -'@backstage/frontend-app-api': patch -'@backstage/config': patch -'@backstage/backend-dynamic-feature-service': patch -'@backstage/core-components': patch -'@backstage/cli': patch -'@backstage/cli-node': patch -'@backstage/catalog-model': patch -'@backstage/integration': patch -'@backstage/frontend-plugin-api': patch -'@backstage/integration-aws-node': patch -'@backstage/repo-tools': patch -'@backstage/backend-test-utils': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-events-backend-module-kafka': patch -'@backstage/plugin-kubernetes-node': patch -'@backstage/plugin-mcp-actions-backend': patch -'@backstage/plugin-scaffolder-node-test-utils': patch -'@backstage/plugin-scaffolder-node': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-user-settings-backend': patch ---- - -Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. diff --git a/.changeset/warm-moments-repeat.md b/.changeset/warm-moments-repeat.md deleted file mode 100644 index a72387d720..0000000000 --- a/.changeset/warm-moments-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Fix #31348 issue where BitbucketUrlReader ignored provided token and instead always used integration credentials diff --git a/.changeset/wild-owls-divide.md b/.changeset/wild-owls-divide.md deleted file mode 100644 index d903b76bd0..0000000000 --- a/.changeset/wild-owls-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fix default font wight and font family in Backstage UI. diff --git a/.cursor/rules/tests/backend-test-utils.mdc b/.cursor/rules/tests/backend-test-utils.mdc new file mode 100644 index 0000000000..857798380d --- /dev/null +++ b/.cursor/rules/tests/backend-test-utils.mdc @@ -0,0 +1,91 @@ +--- +description: Enforce usage of @backstage/backend-test-utils in backend tests +globs: ['**/*.test.*', '**/*.spec.*'] +alwaysApply: false +--- +# Backstage Backend Testing Rules + +**CRITICAL**: When writing tests for Backstage backend code, you MUST use utilities from `@backstage/backend-test-utils` wherever possible. Do NOT create custom mocks, test databases, caches, or service implementations when equivalent utilities exist in backend-test-utils. + +## Required Usage + +1. **Service Mocking**: Always use `mockServices` from `@backstage/backend-test-utils` instead of creating custom service mocks. This includes: + - `mockServices.auth()` - For authentication service mocking + - `mockServices.httpAuth()` - For HTTP authentication service mocking + - `mockServices.database()` - For database service mocking + - `mockServices.cache()` - For cache service mocking + - `mockServices.logger()` - For logger service mocking + - `mockServices.permissions()` - For permissions service mocking + - `mockServices.scheduler()` - For scheduler service mocking + - `mockServices.userInfo()` - For user info service mocking + - `mockServices.events()` - For events service mocking + - `mockServices.urlReader()` - For URL reader service mocking + - `mockServices.rootConfig()` - For root config service mocking + - `mockServices.rootLogger()` - For root logger service mocking + - `mockServices.rootHealth()` - For root health service mocking + - `mockServices.rootLifecycle()` - For root lifecycle service mocking + - `mockServices.httpRouter()` - For HTTP router service mocking + - `mockServices.lifecycle()` - For lifecycle service mocking + - `mockServices.auditor()` - For auditor service mocking + - `mockServices.permissionsRegistry()` - For permissions registry service mocking + +2. **Credentials**: Always use `mockCredentials` from `@backstage/backend-test-utils` for creating test credentials instead of manually constructing credential objects. + +3. **Test Backend**: Always use `startTestBackend` and `TestBackend` from `@backstage/backend-test-utils` when testing backend features, plugins, or modules. This provides a fully configured test backend with all necessary services. + +4. **Service Factory Testing**: Use `ServiceFactoryTester` from `@backstage/backend-test-utils` when testing individual service factories in isolation. + +5. **Database Testing**: Always use `TestDatabases` from `@backstage/backend-test-utils` for database testing. This supports multiple database engines (PostgreSQL, MySQL, SQLite) and handles ephemeral test database creation and cleanup automatically. + +6. **Cache Testing**: Always use `TestCaches` from `@backstage/backend-test-utils` for cache testing. This supports multiple cache backends (Redis, Valkey, Memcache) and handles ephemeral test cache creation and cleanup automatically. + +7. **Filesystem Testing**: Use `createMockDirectory` from `@backstage/backend-test-utils` when testing code that interacts with the filesystem. This provides a safe, isolated temporary directory for tests. + +8. **MSW Integration**: Use `registerMswTestHooks` from `@backstage/backend-test-utils` when setting up Mock Service Worker for HTTP request mocking in tests. + +9. **Error Handler**: Use `mockErrorHandler` from `@backstage/backend-test-utils` when testing Express routers that need error handling middleware. + +10. **Alpha Services**: For alpha/experimental services, use the utilities from `@backstage/backend-test-utils/alpha` (e.g., `actionsRegistryServiceMock`, `actionsServiceMock`). + +## Import Patterns + +Always import from the main package: + +```typescript +import { + mockServices, + mockCredentials, + startTestBackend, + TestDatabases, + TestCaches, + createMockDirectory, + registerMswTestHooks, + mockErrorHandler, + ServiceFactoryTester +} from '@backstage/backend-test-utils'; +``` + +For alpha services: + +```typescript +import { + actionsRegistryServiceMock, + actionsServiceMock +} from '@backstage/backend-test-utils/alpha'; +``` + +## When to Use + +- **Always** use these utilities when writing backend tests +- **Never** create custom mocks for services that have equivalents in backend-test-utils +- **Never** manually set up test databases or caches when TestDatabases/TestCaches are available +- **Never** create custom test backends when startTestBackend is available + +## Benefits + +Using backend-test-utils ensures: +- Consistent test setup across the codebase +- Proper cleanup and resource management +- Support for multiple database/cache backends +- Well-tested and maintained utilities +- Reduced boilerplate in test code diff --git a/.cursor/rules/tests/test-utils.mdc b/.cursor/rules/tests/test-utils.mdc new file mode 100644 index 0000000000..dd66e1f8fa --- /dev/null +++ b/.cursor/rules/tests/test-utils.mdc @@ -0,0 +1,100 @@ +--- +description: Enforce usage of @backstage/test-utils in frontend tests +globs: ['**/*.test.*', '**/*.spec.*'] +alwaysApply: false +--- +# Backstage Frontend/Plugin Testing Rules + +**CRITICAL**: When writing tests for Backstage frontend code, plugins, or React components, you MUST use utilities from `@backstage/test-utils` wherever possible. Do NOT create custom mocks, test wrappers, or API implementations when equivalent utilities exist in test-utils. + +## Required Usage + +1. **API Mocking**: Always use `mockApis` from `@backstage/test-utils` instead of creating custom API mocks. This includes: + - `mockApis.analytics()` - For AnalyticsApi mocking + - `mockApis.config()` - For ConfigApi mocking with optional configuration data + - `mockApis.discovery()` - For DiscoveryApi mocking + - `mockApis.identity()` - For IdentityApi mocking with user entity ref, token, profile info + - `mockApis.permission()` - For PermissionApi mocking with authorization control + - `mockApis.storage()` - For StorageApi mocking with in-memory storage + - `mockApis.translation()` - For TranslationApi mocking (from alpha) + - Each API also provides `.mock()` for jest mocks and `.factory()` for API factories + +2. **Test App Wrappers**: Always use `wrapInTestApp` or `renderInTestApp` from `@backstage/test-utils` when testing React components that need Backstage app context (routing, theme, APIs). Use `createTestAppWrapper` for custom wrapper creation. + +3. **Test API Provider**: Always use `TestApiProvider` or `TestApiRegistry` from `@backstage/test-utils` when testing components that depend on Backstage APIs. This allows you to provide only the APIs needed for your test. + +4. **Async Component Rendering**: Use `renderWithEffects` from `@backstage/test-utils` when testing components that perform asynchronous operations (e.g., useEffect with fetch). This properly handles async rendering with React's act. + +5. **Log Collection**: Use `withLogCollector` from `@backstage/test-utils` when you need to capture and assert on console logs, warnings, or errors during tests. + +6. **MSW Integration**: Use `registerMswTestHooks` from `@backstage/test-utils` when setting up Mock Service Worker for HTTP request mocking in frontend tests. + +7. **Text Content Matcher**: Use `textContentMatcher` from `@backstage/test-utils` as a custom matcher for testing-library queries when matching text content. + +8. **Mock Breakpoint**: Use `mockBreakpoint` from `@backstage/test-utils` when testing components that use Material-UI's `useMediaQuery` hook (though this is deprecated in favor of core-components/testUtils). + +9. **Individual API Mocks**: Use individual mock APIs when you need more control: + - `MockAnalyticsApi` - Analytics API mock + - `MockConfigApi` - Config API mock + - `MockErrorApi` - Error API mock + - `MockFetchApi` - Fetch API mock + - `MockPermissionApi` - Permission API mock + - `MockStorageApi` - Storage API mock + - `MockTranslationApi` - Translation API mock (from alpha) + +10. **Alpha APIs**: For alpha/experimental APIs, use the utilities from `@backstage/test-utils/alpha` (e.g., `MockTranslationApi`). + +## Import Patterns + +Always import from the main package: + +```typescript +import { + mockApis, + wrapInTestApp, + renderInTestApp, + createTestAppWrapper, + TestApiProvider, + TestApiRegistry, + renderWithEffects, + withLogCollector, + registerMswTestHooks, + textContentMatcher, + mockBreakpoint, + MockAnalyticsApi, + MockConfigApi, + MockErrorApi, + MockFetchApi, + MockPermissionApi, + MockStorageApi +} from '@backstage/test-utils'; +``` + +For alpha APIs: + +```typescript +import { + MockTranslationApi +} from '@backstage/test-utils/alpha'; +``` + +## When to Use + +- **Always** use these utilities when writing frontend/plugin tests +- **Always** use `renderInTestApp` or `wrapInTestApp` for components that need Backstage context +- **Always** use `TestApiProvider` or `TestApiRegistry` when components depend on Backstage APIs +- **Always** use `mockApis` instead of creating custom API implementations +- **Never** manually set up React Router, theme providers, or API contexts when test wrappers are available +- **Never** create custom API mocks when mockApis provides equivalents +- **Never** use raw `render` from testing-library when `renderWithEffects` is needed for async components + +## Benefits + +Using test-utils ensures: +- Consistent test setup across frontend codebase +- Proper Backstage app context (routing, theme, APIs) +- Simplified API mocking with realistic implementations +- Proper async rendering handling +- Well-tested and maintained utilities +- Reduced boilerplate in test code +- Better integration with Backstage's plugin architecture diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8491b1a260..40c09a9362 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,6 +11,7 @@ yarn.lock @backstage/maintainers @backst /.changeset/*.md /.github @backstage/operations-maintainers /.github/vale @backstage/documentation-maintainers +/.patches @backstage/operations-maintainers /beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers /docs @backstage/maintainers @backstage/documentation-maintainers /docs/assets/search @backstage/search-maintainers diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 593c7605f5..6697fad858 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -59,6 +59,7 @@ Chanwit CI/CD classname cli +CLIs cloudbuild Cloudflare Cloudformation @@ -110,6 +111,7 @@ dependabot deps dequeue dequeueing +dereferencing deserialization destructured destructuring @@ -148,6 +150,7 @@ etag Expedia facto failover +faq Fargate faqs featureful @@ -229,6 +232,7 @@ JWTs Kaewkasi Kaswell keepalive +Keycloak Keyv Knex knip @@ -323,6 +327,7 @@ onboarded onboarding Onboarding onelogin +openapi OpenSearch OpenShift openssl @@ -504,6 +509,7 @@ thumbsup todo todos togglable +tokenizer tolerations Tolerations toolchain @@ -569,3 +575,4 @@ Zolotusky zoomable zsh resizable +enums diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index f419a12599..654d73b4d4 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -23,7 +23,7 @@ jobs: comment-cache-key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: disable-sudo: true egress-policy: block @@ -32,7 +32,7 @@ jobs: - name: 'Download artifacts' # Fetch output (zip archive) from the workflow run that triggered this workflow. - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ @@ -74,7 +74,7 @@ jobs: - name: Cache Comment if: ${{ steps.event.outputs.ACTION != 'closed' }} - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: comment.md key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} @@ -99,11 +99,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - name: Fetch cached Manifests File id: cache - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: comment.md key: ${{ needs.setup.outputs.comment-cache-key }} diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index f72d9c29bf..934add0716 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -14,11 +14,11 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history @@ -29,13 +29,13 @@ jobs: - name: setup-node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: linux-v20 + cache-prefix: linux-v22 - name: breaking changes check run: | diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index f21ef62d8d..e85a9cf11f 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' sync-labels: true diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index c41f4dd8f3..257db3929a 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -23,11 +23,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 59da292109..83a1e32fcf 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -24,11 +24,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' @@ -44,7 +44,7 @@ jobs: node generate.js ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} > message.txt - name: Post Message - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: ISSUE_NUMBER: ${{ github.event.pull_request.number }} with: diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 5b92c6211b..a7eef4fae4 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Stale check - base - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: stale-issue-message: > This issue has been automatically marked as stale because it has not had @@ -42,7 +42,7 @@ jobs: operations-per-run: 100 - name: Stale check - bugs without repro - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: stale-issue-message: > This bug report has been automatically marked as stale because it has not had diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index 35901d37b2..12aafe18ee 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -19,12 +19,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Verify ${{ matrix.node-version }} steps: # - name: Harden Runner - # uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + # uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.5.1 # with: # egress-policy: audit @@ -35,12 +35,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Test ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24f08544aa..7552898bd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -28,11 +28,11 @@ jobs: name: Install ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -55,7 +55,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -64,11 +64,11 @@ jobs: name: Verify ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -154,7 +154,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Test ${{ matrix.node-version }} services: @@ -210,7 +210,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: fetch master branch run: git fetch origin master @@ -236,7 +236,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore backstage-cli cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/backstage-cli key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }} @@ -260,7 +260,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save backstage-cli cache - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 if: always() with: path: .cache/backstage-cli diff --git a/.github/workflows/cleanup_patch-files.yml b/.github/workflows/cleanup_patch-files.yml new file mode 100644 index 0000000000..3dc0e27837 --- /dev/null +++ b/.github/workflows/cleanup_patch-files.yml @@ -0,0 +1,160 @@ +name: Cleanup Patch Files +on: + push: + branches: + - 'patch/v*' + +concurrency: + group: cleanup-patch-files + cancel-in-progress: false + +jobs: + cleanup: + name: Cleanup Patch Files + runs-on: ubuntu-latest + if: github.repository == 'backstage/backstage' + permissions: + contents: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'Github patch cleanup workflow' + + - name: Extract PR numbers from commit messages + id: extract-pr-numbers + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const { execSync } = require('child_process'); + + // Extract PR numbers from commits pushed to the patch branch + // Commits have messages in format: "Patch from PR #123" + const beforeSha = context.payload.before; + const afterSha = context.payload.after; + + let commitRange; + if (beforeSha && beforeSha !== '0000000000000000000000000000000000000000') { + commitRange = `${beforeSha}..${afterSha}`; + } else { + // First push to branch, check recent commits + commitRange = `-n 20 ${afterSha}`; + } + + // Get commit messages from git log + let commitMessages; + try { + commitMessages = execSync( + `git log --format=%B ${commitRange}`, + { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] } + ); + } catch (error) { + console.log('No commits found in range'); + commitMessages = ''; + } + + // Extract PR numbers from commit messages matching "Patch from PR #123" + const prNumbers = new Set(); + const prPattern = /Patch from PR #(\d+)/g; + let match; + + while ((match = prPattern.exec(commitMessages)) !== null) { + prNumbers.add(parseInt(match[1], 10)); + } + + // Convert to sorted array + const prNumbersArray = Array.from(prNumbers).sort((a, b) => a - b); + + if (prNumbersArray.length > 0) { + console.log(`Found PR numbers: ${prNumbersArray.join(', ')}`); + core.setOutput('pr_numbers', JSON.stringify(prNumbersArray)); + core.setOutput('has_pr_numbers', 'true'); + } else { + console.log('No PR numbers found in commit messages'); + core.setOutput('pr_numbers', '[]'); + core.setOutput('has_pr_numbers', 'false'); + } + + - name: Checkout master + if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: master + token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + + - name: Delete patch files + if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + PR_NUMBERS: ${{ steps.extract-pr-numbers.outputs.pr_numbers }} + REF_NAME: ${{ github.ref_name }} + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const { execSync } = require('child_process'); + const fs = require('fs'); + const path = require('path'); + + const prNumbers = JSON.parse(process.env.PR_NUMBERS); + const patchesDir = path.join(process.cwd(), '.patches'); + + if (!fs.existsSync(patchesDir)) { + console.log('No .patches directory found, nothing to clean up'); + return; + } + + const deletedFiles = []; + + // Delete patch files for each PR number + for (const prNum of prNumbers) { + const patchFile = path.join(patchesDir, `pr-${prNum}.txt`); + if (fs.existsSync(patchFile)) { + fs.unlinkSync(patchFile); + deletedFiles.push(patchFile); + console.log(`Deleted ${patchFile}`); + } else { + console.log(`Patch file ${patchFile} not found, skipping`); + } + } + + if (deletedFiles.length === 0) { + console.log('No patch files to delete'); + return; + } + + - name: Commit and push changes + if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' + run: | + # Check if there are any changes to commit + if git diff --quiet && git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + + git add .patches/ + git commit -m "chore: remove applied patch files after patch release merge + + Removed patch files for PRs included in patch release to ${{ github.ref_name }}" + + git push origin master + + - name: Summary + if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' + run: | + echo "## Patch Files Cleanup Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Patch release to ${{ github.ref_name }} has been merged." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Patch files have been removed from the master branch." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 285c2e6c41..b695c88c90 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -10,7 +10,7 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index e0f4ec29ed..0cf49b4b8a 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -16,16 +16,16 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x] + node-version: [22.x] steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} @@ -52,7 +52,7 @@ jobs: working-directory: ./example-app - name: Login to GitHub Container Registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index ff0b46f0df..e799bcae00 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -23,12 +23,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: find latest release - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: find-release with: script: | @@ -54,20 +54,20 @@ jobs: result-encoding: string - name: checkout latest release - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: refs/tags/${{ steps.find-release.outputs.result }} - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: build API reference run: yarn build:api-docs @@ -82,7 +82,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} @@ -102,7 +102,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 if: always() with: path: .cache/package-docs @@ -135,23 +135,23 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout master - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: build API reference run: yarn build:api-docs @@ -166,7 +166,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }} @@ -186,7 +186,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 if: always() with: path: .cache/package-docs @@ -240,19 +240,19 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth # Stable docs - name: checkout latest release - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: refs/tags/${{ needs.stable.outputs.release }} @@ -286,7 +286,7 @@ jobs: # Next docs - name: checkout master - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: clean: false @@ -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@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4.7.3 + uses: JamesIves/github-pages-deploy-action@4a3abc783e1a24aeb44c16e869ad83caf6b4cc23 # v4.7.4 with: branch: gh-pages folder: microsite/build diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index e3e109dfe7..b8bc3fd120 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] services: postgres18: @@ -68,7 +68,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -94,7 +94,7 @@ jobs: run: yarn backstage-cli config:check --lax - name: backstage-cli cache - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/backstage-cli key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }} @@ -147,11 +147,11 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index 17c76c19c2..470ba954c1 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -16,13 +16,13 @@ jobs: if: github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit # We need to checkout the `.github/ISSUE_TEMPLATE` for the advanced labeler action to be able to read the templates # While at it we might as well checkout all of `.github` so that the labeling actions don't need to fetch their configs - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 with: sparse-checkout: .github @@ -38,7 +38,7 @@ jobs: # These two steps add labels based on user input in the issue form - name: Parse issue form - uses: stefanbuck/github-issue-parser@2ea9b35a8c584529ed00891a8f7e41dc46d0441e # v3 + uses: stefanbuck/github-issue-parser@25f1485edffc1fee3ea68eb9f59a72e58720ffc4 # v3 id: issue-parser with: template-path: .github/ISSUE_TEMPLATE/.common.yaml diff --git a/.github/workflows/mui-migration-tracker.yml b/.github/workflows/mui-migration-tracker.yml new file mode 100644 index 0000000000..7ed8013a3b --- /dev/null +++ b/.github/workflows/mui-migration-tracker.yml @@ -0,0 +1,70 @@ +name: MUI to BUI Migration Tracker + +on: + schedule: + # Run daily at midnight UTC + - cron: '0 0 * * *' + workflow_dispatch: + # Allow manual triggering + +permissions: + issues: write + contents: read + +jobs: + update-migration-progress: + runs-on: ubuntu-latest + name: Update Migration Progress Issue + + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22.x + registry-url: https://registry.npmjs.org/ # Needed for auth + + - name: yarn install + uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + with: + cache-prefix: ${{ runner.os }}-v22.x + + - name: Run migration analysis + id: analysis + run: | + # Run the migration script and save markdown output + yarn mui-to-bui --markdown > migration-report.md + + # Read the report into an environment variable (escape for GitHub Actions) + echo "REPORT<> $GITHUB_ENV + cat migration-report.md >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Update GitHub Issue + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issueNumber = 31467; + const reportBody = process.env.REPORT; + + try { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: reportBody + }); + + console.log(`✅ Successfully updated issue #${issueNumber}`); + } catch (error) { + console.error(`❌ Error updating issue: ${error.message}`); + throw error; + } diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index d5970deae8..95c9c921f5 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml index 9db0792eaa..bb7bd26c53 100644 --- a/.github/workflows/pr-review-comment.yaml +++ b/.github/workflows/pr-review-comment.yaml @@ -17,13 +17,13 @@ jobs: steps: # Inspired by https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Read PR Number id: pr-number - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 33dd3d00ce..36988f7d07 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,7 +18,7 @@ jobs: if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 232d055072..96cb3368be 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,17 +29,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: 'Run analysis' - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/upload-sarif@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_canon.yml b/.github/workflows/sync_canon.yml index 3b85c1c49d..9da6c80b98 100644 --- a/.github/workflows/sync_canon.yml +++ b/.github/workflows/sync_canon.yml @@ -8,26 +8,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Checkout backstage/docs-ui - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: backstage/docs-ui path: bui-external-docs diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 33cab94280..9719a34886 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 181d48eb38..9e894631cc 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,12 +11,12 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 2 ref: ${{ github.head_ref }} @@ -26,7 +26,7 @@ jobs: git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { promises: fs } = require('fs'); diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml new file mode 100644 index 0000000000..e17a229256 --- /dev/null +++ b/.github/workflows/sync_patch-release.yml @@ -0,0 +1,252 @@ +name: Sync Patch Release +on: + push: + branches: + - master + paths: + - '.patches/**' + pull_request: + branches: + - master + paths: + - '.patches/**' + +concurrency: + group: sync-patch-release + cancel-in-progress: true + +jobs: + sync-patch-release: + name: Sync Patch Release + runs-on: ubuntu-latest + if: github.repository == 'backstage/backstage' + permissions: + contents: write + pull-requests: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 20000 + fetch-tags: true + token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'Github patch release workflow' + + - name: Check for patch files + id: check-patches + run: | + if [ -d ".patches" ] && [ "$(ls -A .patches/pr-*.txt 2>/dev/null)" ]; then + echo "has_patches=true" >> $GITHUB_OUTPUT + echo "Found patch files" + else + echo "has_patches=false" >> $GITHUB_OUTPUT + echo "No patch files found" + fi + + - name: Find existing PR + id: find-pr + if: steps.check-patches.outputs.has_patches == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Find PR with the branch as head + const { data: pulls } = await github.rest.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, + state: 'open', + }); + + if (pulls.length > 0) { + console.log(`Found existing PR: #${pulls[0].number}`); + core.setOutput('pr_number', pulls[0].number.toString()); + core.setOutput('pr_exists', 'true'); + } else { + console.log('No existing PR found'); + core.setOutput('pr_exists', 'false'); + } + + - name: Close PR and delete branch if no patches + if: steps.check-patches.outputs.has_patches == 'false' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Find PR with the branch as head + const { data: pulls } = await github.rest.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, + state: 'open', + }); + + if (pulls.length > 0) { + console.log(`Closing PR #${pulls[0].number}`); + await github.rest.pulls.update({ + owner, + repo, + pull_number: pulls[0].number, + state: 'closed', + }); + } + + // Delete the branch + try { + await github.rest.git.deleteRef({ + owner, + repo, + ref: `heads/${branchName}`, + }); + console.log(`Deleted branch ${branchName}`); + } catch (error) { + if (error.status === 422) { + console.log(`Branch ${branchName} does not exist, skipping deletion`); + } else { + throw error; + } + } + + - name: Install Dependencies + if: steps.check-patches.outputs.has_patches == 'true' + run: yarn --immutable + + - name: Read patch files for PR metadata + if: steps.check-patches.outputs.has_patches == 'true' + id: read-patches + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + // Read patch files from master branch to generate PR body + const fs = require('fs').promises; + const path = require('path'); + + const patchesDir = path.join(process.cwd(), '.patches'); + let files = []; + try { + files = await fs.readdir(patchesDir); + } catch (error) { + console.log(`Error reading patches directory: ${error.message}`); + core.setOutput('pr_body', 'This patch release fixes issues.'); + return; + } + + const patchFiles = files.filter(f => /^pr-\d+\.txt$/.test(f)); + + const patches = []; + for (const file of patchFiles) { + const match = file.match(/^pr-(\d+)\.txt$/); + if (match) { + const prNumber = parseInt(match[1], 10); + const filePath = path.join(patchesDir, file); + let content = ''; + try { + content = await fs.readFile(filePath, 'utf8'); + } catch (error) { + console.log(`Error reading ${file}: ${error.message}`); + } + const description = content.trim() || ''; + patches.push({ prNumber, description }); + } + } + + if (patches.length === 0) { + console.log('No patches found, using default values'); + core.setOutput('pr_body', 'This patch release fixes issues.'); + core.setOutput('pr_numbers', '[]'); + return; + } + + patches.sort((a, b) => a.prNumber - b.prNumber); + const prNumbers = patches.map(p => p.prNumber); + const descriptions = patches.map(p => p.description); + + const descriptionList = descriptions.map(desc => `- ${desc}`).join('\n'); + const body = `This patch release fixes the following issues:\n\n${descriptionList}`; + + core.setOutput('pr_body', body); + core.setOutput('pr_numbers', JSON.stringify(prNumbers)); + + - name: Run patch release script + if: steps.check-patches.outputs.has_patches == 'true' + id: run-script + env: + GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + HUSKY: '0' + PATCH_RELEASE_BRANCH: patch-release + run: | + # The script will handle finding the release version, creating the patch branch, + # and deleting patch files. Run the patch script with --from-dir flag to read from .patches/ directory + # The script will write the patch branch to GITHUB_OUTPUT + node scripts/patch-release-for-pr.js --from-dir + + # Ensure the branch is pushed (script pushes it, but force push in case of conflicts) + git push origin patch-release --force + + - name: Create or update PR + if: steps.check-patches.outputs.has_patches == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + PR_EXISTS: ${{ steps.find-pr.outputs.pr_exists }} + PR_NUMBER: ${{ steps.find-pr.outputs.pr_number }} + PR_BODY: ${{ steps.read-patches.outputs.pr_body }} + PATCH_BRANCH: ${{ steps.run-script.outputs.patch_branch }} + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const branchName = 'patch-release'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + const title = 'Patch Release'; + const body = process.env.PR_BODY; + const prExists = process.env.PR_EXISTS === 'true'; + const prNumber = process.env.PR_NUMBER; + const patchBranch = process.env.PATCH_BRANCH; + + if (prExists && prNumber) { + console.log(`Updating PR #${prNumber}`); + await github.rest.pulls.update({ + owner, + repo, + pull_number: parseInt(prNumber, 10), + title, + body, + }); + } else { + // Use the patch branch from the script + if (!patchBranch) { + throw new Error('Patch branch not provided from script. Make sure the script outputs the patch branch name.'); + } + + const baseBranch = patchBranch; + console.log(`Creating new PR with base branch: ${baseBranch}`); + + const { data: pr } = await github.rest.pulls.create({ + owner, + repo, + title, + body, + head: branchName, + base: baseBranch, + }); + console.log(`Created PR #${pr.number}`); + } diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index a9c4b3722b..7452505a22 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -8,26 +8,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # 'v' prefix is added here for the tag, we keep it out of the manifest logic ref: v${{ github.event.client_payload.version }} - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Build yarn plugin working-directory: packages/yarn-plugin @@ -35,7 +35,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: repository: backstage/versions path: versions @@ -71,7 +71,7 @@ jobs: git push - name: Dispatch update-helper update - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} # TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 2331e2c30f..07f2e24697 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,12 +11,12 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 2 ref: ${{ github.head_ref }} @@ -26,7 +26,7 @@ jobs: git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { promises: fs } = require("fs"); diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 3716c1f48b..97f7e8ed14 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,24 +12,24 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: Create Snyk report - uses: snyk/actions/node@77490d94e966421e076e95ad8fa87aa55e5ca409 # master + uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found with: args: > diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 205d146bb8..8b76338645 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,13 +25,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Monitor and Synchronize Snyk Policies - uses: snyk/actions/node@77490d94e966421e076e95ad8fa87aa55e5ca409 # master + uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master with: command: monitor args: > @@ -46,7 +46,7 @@ jobs: # Above we run the `monitor` command, this runs the `test` command which is # the one that generates the SARIF report that we can upload to GitHub. - name: Create Snyk report - uses: snyk/actions/node@77490d94e966421e076e95ad8fa87aa55e5ca409 # master + uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master continue-on-error: true # To make sure that SARIF upload gets called with: args: > @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/upload-sarif@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 66a798bd58..683d527e45 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -12,20 +12,28 @@ jobs: create-release-pr: name: Create Changeset PR runs-on: ubuntu-latest + steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 20000 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: Install Dependencies run: yarn --immutable + - name: Create Release Pull Request uses: backstage/changesets-action@a39baf18913e669734ffb00c2fd9900472cfa240 # v2.3.2 with: diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml index df6f004171..ee696ebc7f 100644 --- a/.github/workflows/verify_accessibility-noop.yml +++ b/.github/workflows/verify_accessibility-noop.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 48e243b002..82d26ca510 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -20,19 +20,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Use Node.js 20.x + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: run Lighthouse CI run: | yarn dlx @lhci/cli@0.11.x autorun diff --git a/.github/workflows/verify_chromatic-noop.yml b/.github/workflows/verify_chromatic-noop.yml index 4de52a3258..35015def52 100644 --- a/.github/workflows/verify_chromatic-noop.yml +++ b/.github/workflows/verify_chromatic-noop.yml @@ -20,7 +20,7 @@ jobs: name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml index 0c9f110b4c..a9d83b01e3 100644 --- a/.github/workflows/verify_chromatic.yml +++ b/.github/workflows/verify_chromatic.yml @@ -19,17 +19,17 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [20.x] + node-version: [22.x] name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 10000 # Required to retrieve git history @@ -64,21 +64,29 @@ jobs: - name: Prepare Chromatic Message if: github.event_name == 'pull_request' && steps.chromatic.outputs.url - id: prepare-message + id: prepareMessage run: | if [ "${{ steps.chromatic.outputs.changeCount }}" = "0" ] || [ -z "${{ steps.chromatic.outputs.changeCount }}" ]; then - echo "changes-text=No visual changes detected - [**View build in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT + echo "changesText=No visual changes detected - [**View build in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT else - echo "changes-text=**${{ steps.chromatic.outputs.changeCount }}** visual changes are waiting for review - [**Review changes in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT + echo "changesText=**${{ steps.chromatic.outputs.changeCount }}** visual changes are waiting for review - [**Review changes in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT fi - - name: Post Chromatic Link in PR Comment + - name: Post Chromatic Results to Job Summary if: github.event_name == 'pull_request' && steps.chromatic.outputs.url + run: | + echo "## 🎨 Visual Testing with Chromatic" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- ${{ steps.prepareMessage.outputs.changesText }}" >> $GITHUB_STEP_SUMMARY + echo "- **${{ steps.chromatic.outputs.specCount}}** stories for **${{ steps.chromatic.outputs.componentCount}}** Components - [**Preview changes in Storybook**](${{ steps.chromatic.outputs.storybookUrl }})" >> $GITHUB_STEP_SUMMARY + + - name: Post Chromatic Link in PR Comment + if: github.event_name == 'pull_request' && steps.chromatic.outputs.url && github.event.pull_request.head.repo.full_name == github.repository uses: mshick/add-pr-comment@v2 with: message: | ## 🎨 Visual Testing with Chromatic - - ${{ steps.prepare-message.outputs.changes-text }} + - ${{ steps.prepareMessage.outputs.changesText }} - **${{ steps.chromatic.outputs.specCount}}** stories for **${{ steps.chromatic.outputs.componentCount}}** Components - [**Preview changes in Storybook**](${{ steps.chromatic.outputs.storybookUrl }}) repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 254938ecc8..d06c8ea270 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -42,12 +42,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/init@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6 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@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/autobuild@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6 # ℹ️ 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@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/analyze@497990dfed22177a82ba1bbab381bc8f6d27058f # v3.31.6 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 6c855eeea4..8810f05a16 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -12,11 +12,11 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index aa3358f2f7..152f4cc2c0 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -24,12 +24,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index dc81d42e8a..436995ddc2 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -43,11 +43,11 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index e933efe3f8..07c751e098 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -32,11 +32,11 @@ jobs: name: Techdocs steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml index b1a4344bfc..f04748a950 100644 --- a/.github/workflows/verify_e2e-windows-noop.yml +++ b/.github/workflows/verify_e2e-windows-noop.yml @@ -20,12 +20,12 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index eefcf22b35..933bbb0840 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -33,7 +33,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit @@ -44,7 +44,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 9c811fbe9f..681ac2c07a 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index 3466826cae..70686e3bd9 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -21,7 +21,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index f3d48ef480..20a6e705cd 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,12 +28,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: find latest release - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 id: find-release with: script: | @@ -59,20 +59,20 @@ jobs: result-encoding: string - name: checkout latest release - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: refs/tags/${{ steps.find-release.outputs.result }} - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: build API reference run: yarn build:api-docs @@ -87,7 +87,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} @@ -105,7 +105,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 if: always() with: path: .cache/package-docs @@ -137,23 +137,23 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout master - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 with: - cache-prefix: ${{ runner.os }}-v20.x + cache-prefix: ${{ runner.os }}-v22.x - name: build API reference run: yarn build:api-docs @@ -168,7 +168,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }} @@ -186,7 +186,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 if: always() with: path: .cache/package-docs @@ -234,16 +234,16 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: '3.9' @@ -295,7 +295,7 @@ jobs: run: yarn build:api-docs - name: checkout master - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: clean: false diff --git a/.github/workflows/verify_microsite_accessibility-noop.yml b/.github/workflows/verify_microsite_accessibility-noop.yml index 5e0bc35cc0..bb13a85dfa 100644 --- a/.github/workflows/verify_microsite_accessibility-noop.yml +++ b/.github/workflows/verify_microsite_accessibility-noop.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 40e1866b5f..0c428ae359 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -15,16 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 20.x + node-version: 22.x - name: top-level install run: yarn install --immutable diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 363bc075b8..ae2ec477d3 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true @@ -29,11 +29,11 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml new file mode 100644 index 0000000000..9061b70d79 --- /dev/null +++ b/.github/workflows/welcome.yml @@ -0,0 +1,42 @@ +name: Add a welcome comment + +on: + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: write + contents: read + +jobs: + welcome: + runs-on: ubuntu-latest + if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + steps: + - name: Add a welcome comment + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request + const login = pr.user.login + + const message = ` + Hi @${login}, thanks for opening your first pull request in Backstage! 👋 + + A couple of useful links to help you get started: + + * [Contributing Guide](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) + * [DCO Sign-off Instructions](https://github.com/backstage/backstage/blob/master/DCO) + * [Style Guide](https://github.com/backstage/backstage/blob/master/STYLE.md) + + We really appreciate your contribution and look forward to reviewing your work. Welcome aboard! + ` + + await github.rest.issues.createComment({ + issue_number: pr.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }) diff --git a/.patches/README.md b/.patches/README.md new file mode 100644 index 0000000000..19b599d6bb --- /dev/null +++ b/.patches/README.md @@ -0,0 +1,16 @@ +# Patch Release Process + +This directory tracks patches to be applied to the next patch release. It contains a list of patch files of the format `pr-.txt` that define which pull requests should be included in the next patch release, where the content of the file is the description of the fix. + +The [sync_patch-release.yml](/.github/workflows/sync_patch-release.yml) workflow will automatically create a "Patch Release" PR with the patches in this directory. + +## Usage + +To add a PR to the set of patches, run `yarn patch-pr ` in the root of the repository. + +## GitHub Workflow + +A GitHub workflow automatically keeps a "Patch Release" PR in sync with the patches listed in this directory. When you add, modify, or remove patch files, the workflow will: + +- Update the existing PR if patch files exist +- Close and delete the PR branch if no patch files exist diff --git a/.patches/create-pr-patch.js b/.patches/create-pr-patch.js new file mode 100755 index 0000000000..528d4fc404 --- /dev/null +++ b/.patches/create-pr-patch.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const prNumber = process.argv[2]; +const description = process.argv.slice(3).join(' '); + +if (!prNumber || !description) { + console.error('Usage: yarn patch-pr '); + process.exit(1); +} + +const patchFilePath = path.join(__dirname, `pr-${prNumber}.txt`); +fs.writeFileSync(patchFilePath, description); diff --git a/ADOPTERS.md b/ADOPTERS.md index 208cd370ba..2b3cad0b25 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -94,7 +94,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | -| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@jamesdabbs-procore](https://github.com/jamesdabbs-procore) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | +| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@kurtaking](https://github.com/kurtaking), [@pc-bob](https://github.com/pc-bob) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | | [Bradesco](https://banco.bradesco/html/classic/sobre/index.shtm) | [@danilosoarescardoso](https://github.com/danilosoarescardoso) | Centralized developer portal with software catalog, software templates, techdocs and some plugins from community and by our own. | | [SeatGeek](https://seatgeek.com) | [@zhammer](https://github.com/zhammer) | Software catalog and documentation platform | | [Grupo Boticário](https://www.grupoboticario.com.br/) | [@chicoribas](https://github.com/chicoribas), [@fndiaz](https://github.com/fndiaz), [@digogid](https://github.com/digogid), [@manumbs](https://github.com/manumbs), [@haooliveira84](https://github.com/haooliveira84), [@Rhullyam](https://github.com/Rhullyam) | Centralized developer portal with catalog, documentation, and software templates to build a ready to go application, including pipelines (CI/CD) and cloud services provisioning | @@ -287,3 +287,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Syngenta Digital](https://www.syngentadigital.com) | [Bitan Mallick](https://www.linkedin.com/in/bitanmallick) | Internal developer portal, designed to empower developers and streamline workflows. We use software catalog, tech-radar, software templates, tech-docs and various custom plugins to ensure an efficient and collaborative development experience. | | [Sophotech](https://sopho.tech) | [@archy-rock3t-cloud](https://github.com/archy-rock3t-cloud), [Artem Muterko](mailto:artem@sopho.tech) | Custom Developer Platform based on Backstage, providing a service catalog, infrastructure templates, and integrated tooling to give developers a self-service experience. | | [Swiss Mobiliar Insurance Company Ltd.](https://www.mobiliar.ch/) | [Patrick Wyler](mailto:patrick.wyler@mobiliar.ch) and [Beat Winistörfer](mailto:beat.winistoerfer@mobiliar.ch) | The portal provides a unified interface for accessing all relevant DevOps information previously scattered across various locations, enhancing accessibility and clarity for all IT employees. It relies on an internal graph database that enhances the Backstage software catalog with many additional elements. Significant effort has been invested in the visual representation of information through graphs and diagrams, facilitating analysis and improving the understanding of dependencies. | +| [DB Systel GmbH](https://www.dbsystel.de/dbsystel-en/) | [DB Systel GmbH](https://github.com/dbsystel) | Deutsche Bahn's Internal Developer Portal leverages Backstage and Crossplane.io to deliver a fully GitOps-driven onboarding experience for engineering teams across the company. Our Scaffolder ecosystem accelerates platform setup (Artifactory, GitLab, OpenShift), service bootstrapping, automated testing, AI-ready backends, and modern frontend development. We also make heavy use of custom catalog modules and frontend plugins that support developers in checking their provisioned resources or security and compliance of their code. | diff --git a/OWNERS.md b/OWNERS.md index d4ee3c6400..44b4ebca15 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -45,6 +45,20 @@ Scope: The catalog plugin and catalog model | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | | Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +### Community Plugins + +Team: @backstage/community-plugins-maintainers + +Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) + +| Name | Organization | GitHub | Discord | +| -------------------- | ------------- | ------------------------------------------- | ------------ | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | +| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | +| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | + ### Design System Team: @backstage/design-system-maintainers @@ -206,20 +220,6 @@ Scope: The auditor core service, along with auditor usage in the main repository | ------------ | ------------ | ----------------------------------------------- | -------------- | | Paul Schultz | Red Hat | [schultzp2020](https://github.com/schultzp2020) | `schultzp2020` | -### Community Plugins - -Team: @backstage/community-plugins-maintainers - -Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) - -| Name | Organization | GitHub | Discord | -| -------------------- | ------------- | ------------------------------------------- | ------------ | -| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | -| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | -| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | -| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | -| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | - ### Events Team: @backstage/events-maintainers diff --git a/README-fr_FR.md b/README-fr_FR.md index a54e9c8c49..27a7cd1153 100644 --- a/README-fr_FR.md +++ b/README-fr_FR.md @@ -41,7 +41,7 @@ Pour commencer à utiliser Backstage, consultez [Getting Started documentation]( ## Documentation -La documentation de Backstage inclus: +La documentation de Backstage inclut: - [Main documentation](https://backstage.io/docs) - [Software Catalog](https://backstage.io/docs/features/software-catalog/) diff --git a/README.md b/README.md index ba44ca951d..e3e9bd77dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!NOTE] -> 🏖 From Monday October 27th through November 3rd, maintainers and Spotify employees will be on vacation due to Wellness Week. Expect the project to move a little slower than normal, and support to be limited. Normal service will resume after that! 🏝 - [![headline](docs/assets/headline.png)](https://backstage.io/) # [Backstage](https://backstage.io) diff --git a/app-config.yaml b/app-config.yaml index b45255d26c..c1a894dcc7 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -110,7 +110,7 @@ integrations: ### Example for how to add a bitbucket cloud integration # bitbucketCloud: # - username: ${BITBUCKET_USERNAME} - # appPassword: ${BITBUCKET_APP_PASSWORD} + # token: ${BITBUCKET_API_TOKEN} ### Example for how to add your bitbucket server instance using the API: # - host: server.bitbucket.com # apiBaseUrl: server.bitbucket.com @@ -209,6 +209,11 @@ scaffolder: email: scaffolder@backstage.io # Use to customize the default commit message when new components are created defaultCommitMessage: 'Initial commit' + defaultEnvironment: + parameters: + region: eu-west-1 + secrets: + environment: ${NODE_ENV} auth: experimentalDynamicClientRegistration: diff --git a/contrib/catalog/ImmediateEntityProvider.ts b/contrib/catalog/ImmediateEntityProvider.ts index b5580b775a..9f9f986567 100644 --- a/contrib/catalog/ImmediateEntityProvider.ts +++ b/contrib/catalog/ImmediateEntityProvider.ts @@ -1,3 +1,8 @@ +import { + coreServices, + createBackendModule, + LoggerService, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -10,50 +15,18 @@ import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-node'; -import { parseEntityYaml } from '@backstage/plugin-catalog-backend'; +import { parseEntityYaml } from '@backstage/plugin-catalog-node'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import bodyParser from 'body-parser'; import express from 'express'; import Router from 'express-promise-router'; import lodash from 'lodash'; -import { Logger } from 'winston'; /** * An entity provider attached to a router, that lets users perform direct * manipulation of a set of entities using REST requests. - * - * @remarks - * - * Installation: - * - * Add it to the catalog builder in your - * `packages/backend/src/plugins/catalog.ts`. Note that it BOTH adds a provider - * and amends the catalog router: - * - * ``` - * const immediate = new ImmediateEntityProvider({ - * logger: env.logger, - * handleEntity: (deferred) => { - * // Optionally modify the incoming entity - * }, - * }); - * builder.addEntityProvider(immediate); - * - * // ... - * - * return router.use('/immediate', immediate.getRouter()); - * ``` - * - * API (assume a catalog prefix, e.g. `/api/catalog`): - * - * - `POST /immediate/entities`: Accepts a YAML document of entities, and - * inserts or updates the entities that match that document. Returns 201 OK on - * success. - * - * - `PUT /immediate/entities`: Accepts a YAML document of entities, and - * replaces the entire set of entities managed by the provider with those - * entities. Returns 201 OK on success. */ -export class ImmediateEntityProvider implements EntityProvider { +class ImmediateEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; private readonly entityValidator: (data: unknown) => Entity; @@ -76,7 +49,7 @@ export class ImmediateEntityProvider implements EntityProvider { router.use(bodyParser.raw({ type: '*/*' })); - router.post('/entities', async (req, res) => { + router.post('/immediate/entities', async (req, res) => { if (!this.connection) { throw new Error(`Service is not yet initialized`); } @@ -89,7 +62,7 @@ export class ImmediateEntityProvider implements EntityProvider { res.status(201).end(); }); - router.put('/entities', async (req, res) => { + router.put('/immediate/entities', async (req, res) => { if (!this.connection) { throw new Error(`Service is not yet initialized`); } @@ -151,19 +124,59 @@ export class ImmediateEntityProvider implements EntityProvider { /** * Options for {@link ImmediateEntityProvider}. */ -export interface ImmediateEntityProviderOptions { +interface ImmediateEntityProviderOptions { /** - * The logger to use. + * The logger. */ - logger: Logger; + logger: LoggerService; /** - * An optional function to perform adjustments to, or validate, an incoming - * entity before being stored. It is permitted to modify the deferred entity, - * but the request is static and has had its body consumed. + * An optional callback function to perform adjustments to, or validate, an + * incoming entity before being stored. It is permitted to modify the deferred + * entity, but the request is static and has had its body consumed. */ handleEntity?: ( request: express.Request, deferred: DeferredEntity, ) => void | Promise; } + +/** + * Backend module that installs an immediate entity provider. + * + * @remarks + * + * Install it by doing `backend.add(immediateEntityProviderModule)` in your `packages/backend/src/index.ts` file. + * + * API: + * + * - `POST /api/catalog/immediate/entities`: Accepts a YAML document of entities, and + * inserts or updates the entities that match that document. Returns 201 OK on + * success. + * + * - `PUT /api/catalog/immediate/entities`: Accepts a YAML document of entities, and + * replaces the entire set of entities managed by the provider with those + * entities. Returns 201 OK on success. + */ +export const immediateEntityProviderModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'immediate-entity-provider', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + router: coreServices.httpRouter, + catalogProcessing: catalogProcessingExtensionPoint, + }, + async init({ logger, router, catalogProcessing }) { + const provider = new ImmediateEntityProvider({ + logger, + // add handleEntity here if you need to modify incoming entities before they are stored + }); + + catalogProcessing.addEntityProvider(provider); + router.use(provider.getRouter()); + }, + }); + }, +}); diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild index 47fd506a34..6c87681391 100644 --- a/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild +++ b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild @@ -6,7 +6,7 @@ # This dockerfile also performs the build first inside docker. This may come # with a build time impact, but is sometimes desirable. If you want to run the -# build on the host instead, use the file simply named Dockerfile in this folder +# build on the host instead, use the file simply named Dockerfile.hostbuild in this folder # instead. # USAGE: diff --git a/contrib/docs/tutorials/heroku-deployment.md b/contrib/docs/tutorials/heroku-deployment.md index 2dea1f021c..8d0b8740b7 100644 --- a/contrib/docs/tutorials/heroku-deployment.md +++ b/contrib/docs/tutorials/heroku-deployment.md @@ -49,7 +49,7 @@ Add a build script in `package.json` to compile frontend during deployment: ## Start Command -Create a [Procfile](https://devcenter.heroku.com/articles/procfile) in the app's root: +Create a [`Procfile`](https://devcenter.heroku.com/articles/procfile) in the app's root: ```shell echo "web: yarn workspace backend start --config ../../app-config.yaml --config ../../app-config.production.yaml" > Procfile diff --git a/docker-compose.deps.yml b/docker-compose.deps.yml index c7515eca04..fa7937fdf1 100644 --- a/docker-compose.deps.yml +++ b/docker-compose.deps.yml @@ -2,7 +2,7 @@ name: backstage services: psql: - image: postgres:17.6 + image: postgres:17.7 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -24,7 +24,7 @@ services: timeout: 3s retries: 5 opensearch: - image: opensearchproject/opensearch:2.19.3 + image: opensearchproject/opensearch:2.19.4 environment: plugins.security.disabled: true discovery.type: single-node diff --git a/docs-ui/README.md b/docs-ui/README.md index 41b9921413..b943f1ce7c 100644 --- a/docs-ui/README.md +++ b/docs-ui/README.md @@ -13,3 +13,28 @@ yarn start ## Deployment Deployments are done automatically when a PR is merged into the `master` branch. We host the website using Github pages. + +## Maintaining Component Changelogs + +After a `@backstage/ui` release, sync the component changelogs to keep documentation up-to-date: + +```bash +yarn sync:changelog +``` + +This script: + +- Parses `packages/ui/CHANGELOG.md` for new versions +- Extracts entries tagged with "Affected components: ..." +- Updates `src/utils/changelog.ts` with new entries +- Handles both component-specific and general package changes + +After running, review the changes in `src/utils/changelog.ts` and commit them. + +**Preview changes before writing:** + +```bash +yarn sync:changelog:dry-run +``` + +Running this gives you a summary of what would be written, without actually adding or changing any files. diff --git a/docs-ui/next.config.mjs b/docs-ui/next.config.mjs index 8e6ed8fdc0..2ff341b609 100644 --- a/docs-ui/next.config.mjs +++ b/docs-ui/next.config.mjs @@ -1,4 +1,6 @@ import createMDX from '@next/mdx'; +import path from 'path'; +import { fileURLToPath } from 'url'; const nextConfig = { pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], @@ -13,6 +15,10 @@ const nextConfig = { // These are type-level conflicts that don't affect runtime behavior ignoreBuildErrors: true, }, + outputFileTracingRoot: path.join( + path.dirname(fileURLToPath(import.meta.url)), + '..', + ), }; const withMDX = createMDX({}); diff --git a/docs-ui/package.json b/docs-ui/package.json index 17c13f64f3..ff5d59e7f0 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -7,7 +7,9 @@ "build": "next build", "lint": "next lint", "prestart": "yarn sync:css", - "start": "concurrently \"yarn sync:css:watch\" \"next dev\"", + "start": "next dev", + "sync:changelog": "node scripts/sync-changelog.mjs", + "sync:changelog:dry-run": "node scripts/sync-changelog.mjs --dry-run", "sync:css": "node scripts/sync-css.js", "sync:css:watch": "node scripts/sync-css.js --watch" }, @@ -16,19 +18,21 @@ "@types/react-dom": "19.1.7" }, "dependencies": { + "@base-ui-components/react": "^1.0.0-beta.4", "@codemirror/lang-sass": "^6.0.2", "@codemirror/view": "^6.34.4", "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "15.3.4", + "@next/mdx": "15.5.7", "@remixicon/react": "^4.6.0", "@storybook/react": "^8.6.12", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", + "clsx": "^2.1.1", "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "15.4.7", + "next": "15.5.7", "next-mdx-remote-client": "^2.1.2", "prop-types": "^15.8.1", "react": "19.1.1", @@ -37,16 +41,17 @@ "storybook": "^8.6.12" }, "devDependencies": { + "@octokit/rest": "^22.0.1", "@shikijs/transformers": "^3.13.0", "@types/mdx": "^2.0.13", - "@types/node": "^20", + "@types/node": "^22.13.14", "@types/react": "19.1.9", "@types/react-dom": "19.1.7", "chokidar": "^3.6.0", - "concurrently": "^8.2.2", "eslint": "^8", - "eslint-config-next": "15.3.4", + "eslint-config-next": "15.5.7", "lightningcss": "^1.28.2", - "typescript": "^5" + "typescript": "^5", + "unified": "^11.0.4" } } diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs new file mode 100644 index 0000000000..5eab3616dd --- /dev/null +++ b/docs-ui/scripts/sync-changelog.mjs @@ -0,0 +1,991 @@ +#!/usr/bin/env node +/* eslint-disable no-restricted-syntax */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import { Octokit } from '@octokit/rest'; +import { execSync } from 'child_process'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Compare two semantic versions. + * Returns: 1 if versionA > versionB, -1 if versionA < versionB, 0 if equal + * Handles pre-release versions (e.g., "0.9.0-next.2") + */ +function compareVersions(versionA, versionB) { + // Strip 'v' prefix if present + versionA = versionA.replace(/^v/, ''); + versionB = versionB.replace(/^v/, ''); + + // Split into [major.minor.patch, prerelease] + const [versionABase, versionAPre] = versionA.split('-'); + const [versionBBase, versionBPre] = versionB.split('-'); + + // Compare base versions + const versionAParts = versionABase.split('.').map(Number); + const versionBParts = versionBBase.split('.').map(Number); + + for ( + let i = 0; + i < Math.max(versionAParts.length, versionBParts.length); + i++ + ) { + const versionAPart = versionAParts[i] || 0; + const versionBPart = versionBParts[i] || 0; + + if (versionAPart > versionBPart) return 1; + if (versionAPart < versionBPart) return -1; + } + + // If base versions equal, compare pre-release + if (!versionAPre && !versionBPre) return 0; + if (!versionAPre) return 1; // versionA is release, versionB is pre-release + if (!versionBPre) return -1; // versionA is pre-release, versionB is release + + // Both have pre-release, compare them + return versionAPre.localeCompare(versionBPre); +} + +/** + * Check if versionA is newer than versionB + */ +function isNewerVersion(versionA, versionB) { + return compareVersions(versionA, versionB) > 0; +} + +/** + * Normalize version to base version (remove pre-release suffix) + * "0.9.0-next.3" → "0.9.0" + * "0.9.0" → "0.9.0" + */ +function normalizeVersion(version) { + return version.split('-')[0]; +} + +/** + * Read the existing changelog.ts and find the highest version number + * by scanning the changelogs/ directory for version files + */ +function getLastSyncedVersion(changelogPath) { + try { + // Scan changelogs/ directory for latest version file + const changelogsDir = path.join(path.dirname(changelogPath), 'changelogs'); + + if (!fs.existsSync(changelogsDir)) { + return null; + } + + // Read all version files (v0.1.0.ts, v0.2.0.ts, etc.) + const files = fs + .readdirSync(changelogsDir) + .filter(f => f.match(/^v\d+\.\d+\.\d+\.ts$/)) + .map(f => f.replace(/^v/, '').replace(/\.ts$/, '')); + + if (files.length === 0) { + return null; + } + + // Find highest version + let highest = files[0]; + for (const version of files) { + if (compareVersions(version, highest) > 0) { + highest = version; + } + } + + return highest; + } catch (error) { + console.error(`Error reading changelog file: ${error.message}`); + return null; + } +} + +/** + * Convert PascalCase component name to kebab-case slug + * Avatar -> avatar + * ButtonIcon -> button-icon + * SearchField -> searchfield (special case per existing data) + */ +function pascalToKebab(str) { + // Special cases observed in changelog.ts + const specialCases = { + SearchField: 'searchfield', + TextField: 'textfield', + DataTable: 'datatable', + ScrollArea: 'scrollarea', + }; + + if (specialCases[str]) { + return specialCases[str]; + } + + // General conversion: insert hyphen before uppercase letters + return str + .replace(/([A-Z])/g, '-$1') + .toLowerCase() + .replace(/^-/, ''); // Remove leading hyphen +} + +/** + * Map component name to valid Component type slug + * Returns null if component not recognized + */ +function mapComponentName(name, validComponents) { + const kebab = pascalToKebab(name.trim()); + const kebabLower = kebab.toLowerCase(); + + // Check if it's a valid component + const valid = validComponents.find(c => c.toLowerCase() === kebabLower); + + return valid || null; +} + +/** + * Get valid component names from types.ts Component type + */ +function getValidComponents(changelogPath) { + // Read from types.ts instead of changelog.ts + const typesPath = changelogPath.replace('changelog.ts', 'types.ts'); + const content = fs.readFileSync(typesPath, 'utf-8'); + + // Extract Component type union + const typeMatch = content.match(/export type Component =([^;]+);/s); + if (!typeMatch) { + throw new Error('Could not find Component type definition'); + } + + // Extract string literals from the union + const components = typeMatch[1] + .match(/['"]([^'"]+)['"]/g) + .map(s => s.replace(/['"]/g, '')); + + return components; +} + +/** + * Parse CHANGELOG.md and extract entries newer than sinceVersion + */ +async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { + const content = fs.readFileSync(changelogPath, 'utf-8'); + const tree = unified().use(remarkParse).parse(content); + + const entries = []; + let currentVersion = null; + let currentSection = null; // 'Minor Changes', 'Patch Changes', 'Major Changes' + let processedCount = 0; + + // Walk through the markdown AST + async function walk(node, depth = 0) { + // Version headers (## 0.9.0) + if (node.type === 'heading' && node.depth === 2) { + const versionText = extractText(node).trim(); + // Validate that this is actually a version number (X.Y.Z or X.Y.Z-pre.N) + // Skip headings that are just markdown content within changelog entries + if (/^\d+\.\d+\.\d+/.test(versionText)) { + currentVersion = versionText; + currentSection = null; + } + return; + } + + // Section headers (### Minor Changes) + if (node.type === 'heading' && node.depth === 3) { + const sectionText = extractText(node); + currentSection = sectionText.trim(); + return; + } + + // List items (- 539cf26: description) + if (node.type === 'listItem' && currentVersion && currentSection) { + // Only process if version is newer than sinceVersion + if (!sinceVersion || isNewerVersion(currentVersion, sinceVersion)) { + const entry = await parseListItem( + node, + currentVersion, + currentSection, + validComponents, + content, + ); + if (entry) { + entries.push(entry); + processedCount++; + // Progress logging every 50 entries + if (processedCount % 50 === 0) { + process.stdout.write(`\r Processed ${processedCount} entries...`); + } + } + } + } + + // Recurse into children + if (node.children) { + for (const child of node.children) { + await walk(child, depth + 1); + } + } + } + + await walk(tree); + + // Clear progress line + if (processedCount > 0) { + process.stdout.write(`\r Processed ${processedCount} entries - done!\n`); + } + + return entries; +} + +/** + * Extract plain text from a markdown node + */ +function extractText(node) { + if (node.type === 'text') { + return node.value; + } + if (node.children) { + return node.children.map(extractText).join(''); + } + return ''; +} + +/** + * Extract raw markdown content from a node using position offsets + * This preserves all formatting: code blocks, lists, bold/italic, etc. + */ +function extractMarkdown(node, sourceText) { + if (!node.position) { + return extractText(node); // Fallback to text extraction + } + + const start = node.position.start.offset; + const end = node.position.end.offset; + + if (start === undefined || end === undefined) { + return extractText(node); + } + + return sourceText.slice(start, end).trim(); +} + +/** + * Parse a list item to extract changelog entry + * NOTE: PR numbers are NOT fetched here - they're fetched later only for new entries + */ +async function parseListItem( + node, + version, + section, + validComponents, + sourceText, +) { + // Extract the full text content for SHA parsing + const fullText = extractText(node); + + // Parse commit SHA and description + // Format: "- 539cf26: description..." + const match = fullText.match(/^-?\s*([a-f0-9]+):\s*(.+)/s); + if (!match) { + return null; + } + + const commitSha = match[1]; + + // Extract full markdown content from the list item + let fullMarkdown = extractMarkdown(node, sourceText); + + // Remove the commit SHA prefix from markdown + let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim(); + + // Auto-detect components from "Affected components:" line + let components = []; + const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i); + if (componentMatch) { + const componentNames = componentMatch[1] + .split(',') + .map(name => name.trim()) + .filter(Boolean); + + components = componentNames + .map(name => mapComponentName(name, validComponents)) + .filter(Boolean); + + // Optionally strip "Affected components:" line from description + description = description + .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '') + .trim(); + } + + const prs = []; // Will be populated later by fetchPRNumbers() + + // Infer type from section and description + const type = inferChangeType(section, description, version); + + return { + version: normalizeVersion(version), + section, + commitSha, + description, + components, + prs, + type, + }; +} + +/** + * Infer change type from section and description + */ +function inferChangeType(section, description, version) { + const isPre1 = version.startsWith('0.'); + + // Check description keywords first + if (description.match(/^(New|Add(ed)?)\s/i)) { + return 'new'; + } + if (description.includes('BREAKING')) { + return 'breaking'; + } + + // Infer from section + if (section === 'Minor Changes') { + return isPre1 ? 'breaking' : undefined; + } else if (section === 'Major Changes') { + return 'breaking'; + } else if (section === 'Patch Changes') { + return 'fix'; + } + + return undefined; +} + +/** + * Generate new changelog entries and update changelog.ts + */ +/** + * Fetch PR numbers for entries that need them + * Only fetches for entries with empty prs array (new entries) + */ +async function fetchPRNumbers(entries, dryRun = false) { + const entriesNeedingPRs = entries.filter( + e => e.prs.length === 0 && e.commitSha, + ); + + if (entriesNeedingPRs.length === 0) { + return; + } + + console.log( + `\n🔍 Fetching PR numbers for ${entriesNeedingPRs.length} new entries...`, + ); + + let fetchedCount = 0; + const startTime = Date.now(); + + for (const entry of entriesNeedingPRs) { + const prNumber = await findPRNumber(entry.commitSha); + if (prNumber) { + entry.prs.push(prNumber); + } + + fetchedCount++; + // Progress every 10 lookups + if (fetchedCount % 10 === 0 || fetchedCount === entriesNeedingPRs.length) { + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + const rate = ((fetchedCount / (Date.now() - startTime)) * 1000).toFixed( + 1, + ); + process.stdout.write( + `\r Fetched ${fetchedCount}/${entriesNeedingPRs.length} PR numbers (${elapsed}s, ~${rate} req/s)`, + ); + } + } + + const totalTime = ((Date.now() - startTime) / 1000).toFixed(1); + const foundCount = entriesNeedingPRs.filter(e => e.prs.length > 0).length; + console.log( + `\n ✓ Found ${foundCount}/${entriesNeedingPRs.length} PR numbers in ${totalTime}s`, + ); +} + +/** + * Generate per-version changelog files in changelogs/ directory + */ +async function generateVersionFiles(entries, changelogsDir, dryRun = false) { + // Group entries by normalized version + const byVersion = {}; + entries.forEach(entry => { + const version = entry.version; + if (!byVersion[version]) { + byVersion[version] = []; + } + byVersion[version].push(entry); + }); + + const versionFiles = []; + const skippedVersions = []; + const generatedVersions = []; + + for (const [version, versionEntries] of Object.entries(byVersion)) { + const fileName = `v${version}.ts`; + const filePath = path.join(changelogsDir, fileName); + const varName = `changelog_${version.replace(/\./g, '_')}`; + + // Check if file already exists - skip if it does + if (fs.existsSync(filePath)) { + skippedVersions.push(version); + // Still add to versionFiles array so main changelog.ts can import it + versionFiles.push({ + version, + fileName, + filePath, + varName, + content: null, // No content needed for existing files + skipped: true, + }); + continue; + } + + // Generate TypeScript code for this version + const entryObjects = versionEntries + .map(entry => { + const componentsStr = `[${entry.components + .map(c => `'${c}'`) + .join(', ')}]`; + const prsStr = `[${entry.prs.map(pr => `'${pr}'`).join(', ')}]`; + const typeStr = entry.type ? `type: '${entry.type}',` : ''; + const shaStr = entry.commitSha + ? `commitSha: '${entry.commitSha}',` + : ''; + + // Escape description for template literal + const descEscaped = entry.description + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\${/g, '\\${'); + + return ` { + components: ${componentsStr}, + version: '${entry.version}', + prs: ${prsStr}, + description: \`${descEscaped}\`, + ${typeStr} + ${shaStr} + }`; + }) + .join(',\n'); + + const fileContent = `import type { ChangelogProps } from '../types'; + +export const ${varName}: ChangelogProps[] = [ +${entryObjects} +]; +`; + + generatedVersions.push(version); + versionFiles.push({ + version, + fileName, + filePath, + varName, + content: fileContent, + skipped: false, + }); + + if (!dryRun) { + fs.writeFileSync(filePath, fileContent, 'utf-8'); + } + } + + // Log summary + if (skippedVersions.length > 0) { + console.log( + `\n⏭️ Skipped ${ + skippedVersions.length + } existing version files: ${skippedVersions + .sort((a, b) => compareVersions(a, b)) + .join(', ')}`, + ); + } + if (generatedVersions.length > 0) { + console.log( + `\n✨ ${dryRun ? 'Would generate' : 'Generated'} ${ + generatedVersions.length + } new version files: ${generatedVersions + .sort((a, b) => compareVersions(a, b)) + .join(', ')}`, + ); + } + + return versionFiles; +} + +/** + * Generate main changelog.ts that imports and spreads all version files + */ +async function generateMainChangelog( + versionFiles, + changelogPath, + dryRun = false, +) { + // Get the changelogs directory path + const changelogsDir = path.join(path.dirname(changelogPath), 'changelogs'); + + // Read all version files from the changelogs directory + const allVersionFiles = new Map(); + + // First, add all files from the directory (if it exists) + if (fs.existsSync(changelogsDir)) { + const files = fs.readdirSync(changelogsDir); + files.forEach(file => { + if (file.match(/^v\d+\.\d+\.\d+\.ts$/)) { + const version = file.replace(/^v/, '').replace(/\.ts$/, ''); + const varName = `changelog_${version.replace(/\./g, '_')}`; + allVersionFiles.set(version, { + version, + fileName: file, + varName, + }); + } + }); + } + + // Then, add/update with files from this run (in case new ones were generated) + versionFiles.forEach(vf => { + allVersionFiles.set(vf.version, { + version: vf.version, + fileName: vf.fileName, + varName: vf.varName, + }); + }); + + // Sort versions in descending order + const sortedVersions = Array.from(allVersionFiles.values()).sort((a, b) => + compareVersions(b.version, a.version), + ); + + const imports = sortedVersions + .map( + v => + `import { ${v.varName} } from './changelogs/${v.fileName.replace( + '.ts', + '', + )}';`, + ) + .join('\n'); + + const spreads = sortedVersions.map(v => ` ...${v.varName},`).join('\n'); + + const content = `export * from './types'; +${imports} + +export const changelog = [ +${spreads} +]; +`; + + console.log( + `\n📝 Main changelog.ts will import all ${sortedVersions.length} version files`, + ); + + if (!dryRun) { + fs.writeFileSync(changelogPath, content, 'utf-8'); + } + + return content; +} + +/** + * Check if an entry already exists in changelog.ts + */ +function isDuplicate(entry, existingContent) { + // Simple check: look for version + description substring + const descStart = entry.description.substring(0, 50); + const pattern = `version: '${entry.version}'`; + + const versionIndex = existingContent.indexOf(pattern); + if (versionIndex === -1) return false; + + // Check if description appears near this version + const contextWindow = existingContent.substring( + Math.max(0, versionIndex - 200), + versionIndex + 500, + ); + + return contextWindow.includes(descStart); +} + +/** + * Detect GitHub authentication method + * Priority: gh CLI → GITHUB_TOKEN env → unauthenticated + */ +function getGitHubAuth() { + // 1. Try gh CLI (preferred - uses keyring auth) + try { + // Check specifically for github.com authentication + const output = execSync('gh auth status -h github.com 2>&1', { + encoding: 'utf-8', + }); + if (output.includes('Logged in to github.com')) { + console.log('✓ Using gh CLI authentication'); + return { method: 'gh-cli' }; + } + } catch { + // gh not installed or not authenticated to github.com + } + + // 2. Try GITHUB_TOKEN env var + if (process.env.GITHUB_TOKEN) { + console.log('✓ Using GITHUB_TOKEN authentication'); + return { method: 'token', token: process.env.GITHUB_TOKEN }; + } + + // 3. Fallback to unauthenticated (60 req/hour limit) + console.warn('⚠️ Using unauthenticated GitHub API (60 requests/hour)'); + console.warn(' For higher limits: authenticate gh CLI or set GITHUB_TOKEN'); + return { method: 'unauthenticated' }; +} + +// Initialize auth and Octokit +const githubAuth = getGitHubAuth(); +const octokit = + githubAuth.method === 'token' + ? new Octokit({ auth: githubAuth.token }) + : new Octokit(); + +// Cache for PR lookups +const prCache = new Map(); + +/** + * Expand a truncated SHA to full SHA using local git repository + * Falls back to GitHub API if git rev-parse fails + */ +async function expandCommitSha(shortSha) { + try { + // First try local git repository (fastest, works for all commits) + // We need to run this from the backstage monorepo root + const repoRoot = path.join(__dirname, '../../'); + + try { + const fullSha = execSync(`git rev-parse ${shortSha}`, { + encoding: 'utf-8', + cwd: repoRoot, + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + + if (fullSha && fullSha.length === 40) { + return fullSha; + } + } catch (gitError) { + // Handle ambiguous SHA - parse git's error output to find commit candidates + if (gitError.stderr && gitError.stderr.includes('ambiguous')) { + const stderr = gitError.stderr.toString(); + // Extract commit SHAs from git's hint output + // Format: "hint: 83c100e6accfb commit 2025-10-22 - message" + const commitMatch = stderr.match(/hint:\s+([a-f0-9]+)\s+commit\s+/); + if (commitMatch && commitMatch[1]) { + // Found the commit SHA from the ambiguous candidates + const candidateSha = commitMatch[1]; + // Verify this is a valid commit + try { + const fullSha = execSync(`git rev-parse ${candidateSha}`, { + encoding: 'utf-8', + cwd: repoRoot, + }).trim(); + if (fullSha && fullSha.length === 40) { + return fullSha; + } + } catch { + // Candidate didn't work, continue to GitHub API + } + } + } + // Commit not in local repo, try GitHub API + } + } catch (error) { + // Error setting up git command, try GitHub API + } + + try { + // Fallback to GitHub API + if (githubAuth.method === 'gh-cli') { + const result = execSync( + `gh api repos/backstage/backstage/commits/${shortSha} --jq '.sha' 2>/dev/null`, + { encoding: 'utf-8' }, + ).trim(); + return result || null; + } else { + // Use Octokit + const { data: commit } = await octokit.rest.repos.getCommit({ + owner: 'backstage', + repo: 'backstage', + ref: shortSha, + }); + return commit.sha; + } + } catch (error) { + // Commit not found or other error - return null silently + return null; + } +} + +/** + * Find PR number for a commit SHA using GitHub API + */ +async function findPRNumber(commitSha) { + // Check cache first + if (prCache.has(commitSha)) { + return prCache.get(commitSha); + } + + try { + // If SHA is truncated (7 chars), expand it first + let fullSha = commitSha; + if (commitSha.length === 7) { + fullSha = await expandCommitSha(commitSha); + if (!fullSha) { + // Commit not found + console.warn(`⚠️ Commit not found: ${commitSha}`); + prCache.set(commitSha, null); + return null; + } + } + + let prNumber = null; + + // Use gh CLI if available (faster, uses existing auth) + if (githubAuth.method === 'gh-cli') { + try { + const result = execSync( + `gh api repos/backstage/backstage/commits/${fullSha}/pulls --jq '.[0].number' 2>/dev/null`, + { encoding: 'utf-8' }, + ).trim(); + prNumber = result || null; + } catch (ghError) { + // Handle gh CLI errors gracefully - commit exists but has no PR + prNumber = null; + } + } else { + // Otherwise use Octokit (token or unauthenticated) + const { data: prs } = + await octokit.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: 'backstage', + repo: 'backstage', + commit_sha: fullSha, + }); + prNumber = prs.length > 0 ? prs[0].number.toString() : null; + } + + // Cache the result + prCache.set(commitSha, prNumber); + return prNumber; + } catch (error) { + if (error.message?.includes('rate limit')) { + console.error('⚠️ GitHub API rate limit exceeded'); + console.error( + ' Authenticate gh CLI or set GITHUB_TOKEN for higher limits', + ); + } else { + console.warn(`⚠️ Error finding PR for ${commitSha}: ${error.message}`); + } + prCache.set(commitSha, null); + return null; + } +} + +/** + * Main sync function + */ +async function main() { + const args = process.argv.slice(2); + const dryRun = args.includes('--dry-run'); + + const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts'); + const changelogMdPath = path.join( + __dirname, + '../../packages/ui/CHANGELOG.md', + ); + + // Validate files exist + if (!fs.existsSync(changelogTsPath)) { + throw new Error(`changelog.ts not found at: ${changelogTsPath}`); + } + if (!fs.existsSync(changelogMdPath)) { + throw new Error(`CHANGELOG.md not found at: ${changelogMdPath}`); + } + + console.log('📋 Syncing UI component changelogs...\n'); + + // Get last synced version + const lastVersion = getLastSyncedVersion(changelogTsPath); + console.log(`Last synced version: ${lastVersion || '(none)'}`); + + // Get valid components + const validComponents = getValidComponents(changelogTsPath); + console.log(`Valid components: ${validComponents.length}`); + + // Parse CHANGELOG.md + console.log('\n📖 Parsing CHANGELOG.md...'); + const allEntries = await parseChangelogMd( + changelogMdPath, + lastVersion, + validComponents, + ); + console.log(`Found ${allEntries.length} total entries since ${lastVersion}`); + + // Read existing changelog content for duplicate detection + const existingContent = fs.readFileSync(changelogTsPath, 'utf-8'); + + // Filter to only new, non-duplicate entries + const relevantEntries = allEntries.filter(e => { + const hasComponents = e.components.length > 0 || e.components.length === 0; + const notDuplicate = !isDuplicate(e, existingContent); + return hasComponents && notDuplicate; + }); + + const duplicatesCount = allEntries.length - relevantEntries.length; + if (duplicatesCount > 0) { + console.log(`Skipped ${duplicatesCount} duplicate entries`); + } + + console.log( + `Relevant entries (with or without components): ${relevantEntries.length}`, + ); + + if (relevantEntries.length === 0) { + console.log('\n✅ No new entries to sync'); + return; + } + + // Fetch PR numbers for new entries (lazy fetch - only for entries that will be written) + await fetchPRNumbers(relevantEntries, dryRun); + + // Show summary + console.log('\n📝 New entries by component:'); + const byComponent = {}; + relevantEntries.forEach(entry => { + if (entry.components.length === 0) { + byComponent['(general)'] = (byComponent['(general)'] || 0) + 1; + } else { + entry.components.forEach(comp => { + byComponent[comp] = (byComponent[comp] || 0) + 1; + }); + } + }); + Object.entries(byComponent).forEach(([comp, count]) => { + console.log(` - ${comp}: ${count} ${count === 1 ? 'entry' : 'entries'}`); + }); + + // Warn about unknown components + const unknownComponents = []; + allEntries.forEach(entry => { + const fullText = entry.description; + const componentMatch = fullText.match( + /Affected components?:[ \t]*([^\n]+)/i, + ); + if (componentMatch) { + const names = componentMatch[1].split(',').map(n => n.trim()); + names.forEach(name => { + if (!mapComponentName(name, validComponents)) { + unknownComponents.push(name); + } + }); + } + }); + + if (unknownComponents.length > 0) { + console.log('\n⚠️ Unknown components (skipped):'); + [...new Set(unknownComponents)].forEach(name => { + console.log(` - ${name}`); + }); + } + + // Create changelogs directory if it doesn't exist + const changelogsDir = path.join(__dirname, '../src/utils/changelogs'); + if (!fs.existsSync(changelogsDir) && !dryRun) { + fs.mkdirSync(changelogsDir, { recursive: true }); + } + + // Generate version files + console.log( + `\n${ + dryRun ? '🔍 Dry run - would generate' : '✍️ Generating' + } version files...`, + ); + + const versionFiles = await generateVersionFiles( + relevantEntries, + changelogsDir, + dryRun, + ); + + // Generate main changelog.ts + const mainContent = await generateMainChangelog( + versionFiles, + changelogTsPath, + dryRun, + ); + + if (!dryRun) { + console.log('\n✅ Changelog sync complete!'); + const newFiles = versionFiles.filter(vf => !vf.skipped).length; + const skippedFiles = versionFiles.filter(vf => vf.skipped).length; + if (newFiles > 0) { + console.log( + ` Generated ${newFiles} new version file${newFiles === 1 ? '' : 's'}`, + ); + } + if (skippedFiles > 0) { + console.log( + ` Preserved ${skippedFiles} existing version file${ + skippedFiles === 1 ? '' : 's' + }`, + ); + } + console.log(` Updated ${changelogTsPath}`); + } else { + console.log('\n📄 Sample version file content:\n'); + // Find first non-skipped version file for sample + const sampleFile = versionFiles.find(vf => !vf.skipped && vf.content); + if (sampleFile) { + const sample = sampleFile.content.split('\n').slice(0, 30).join('\n'); + console.log(sample); + console.log('...\n'); + } else { + console.log('(No new version files to generate)\n'); + } + + console.log('\n📄 Main changelog.ts content:\n'); + console.log(mainContent); + + const newFiles = versionFiles.filter(vf => !vf.skipped).length; + const skippedFiles = versionFiles.filter(vf => vf.skipped).length; + + console.log('\n✅ Dry run complete - no files were modified'); + if (newFiles > 0) { + console.log( + ` Run without --dry-run to generate ${newFiles} new version file${ + newFiles === 1 ? '' : 's' + }`, + ); + } + if (skippedFiles > 0) { + console.log( + ` ${skippedFiles} existing version file${ + skippedFiles === 1 ? '' : 's' + } will be preserved`, + ); + } + } +} + +// Run main if executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch(error => { + console.error('❌ Error:', error.message); + process.exit(1); + }); +} diff --git a/docs-ui/src/app/about/page.mdx b/docs-ui/src/app/about/page.mdx deleted file mode 100644 index 2c87a3d03e..0000000000 --- a/docs-ui/src/app/about/page.mdx +++ /dev/null @@ -1,32 +0,0 @@ -# About Backstage UI - -Backstage UI is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS. -This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Backstage UI -is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users. - -## Philosophy - -Backstage empowers product teams to build software faster and with greater quality. Its extensibility, -however, required us to rethink how to deliver a consistent and accessible user experience. Our goal is -to enable plugin creators to design plugins that seamlessly integrate with Backstage's look and feel while -still allowing customization to match individual brands. - -Instead of reinventing the wheel, we chose to focus on layout and styling while leveraging existing headless -component libraries for functionality. This approach allows us to dedicate our efforts to creating a cohesive -and flexible theming system. - -## Team - -Backstage UI is designed and maintained primarily by Spotify's Backstage team, leveraging Spotify's expertise in -crafting high-quality design and technology. Drawing from our experience in building reliable and intuitive -user experiences for the music industry, we've created a design system that looks great and works seamlessly. - -## Community - -Backstage UI is an open-source project and we welcome contributions from the community. If you are interested in -contributing to Backstage UI, please read our [contributing guide](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) -and our [code of conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md). - -## License - -Backstage UI is licensed under the Apache 2.0 license. See the [LICENSE](https://github.com/backstage/backstage/blob/master/LICENSE) file for more details. diff --git a/docs-ui/src/app/components/[slug]/page.tsx b/docs-ui/src/app/components/[slug]/page.tsx index d6f98c8f93..1422334471 100644 --- a/docs-ui/src/app/components/[slug]/page.tsx +++ b/docs-ui/src/app/components/[slug]/page.tsx @@ -7,9 +7,7 @@ export default async function Page({ }) { const { slug } = await params; - const { default: Component } = await import( - `@/content/components/${slug}.mdx` - ); + const { default: Component } = await import(`@/content/${slug}.mdx`); return ; } diff --git a/docs-ui/src/app/components/page.mdx b/docs-ui/src/app/components/page.mdx new file mode 100644 index 0000000000..bd18005d19 --- /dev/null +++ b/docs-ui/src/app/components/page.mdx @@ -0,0 +1,173 @@ +import { ComponentCards, ComponentCard } from '@/components/ComponentCards'; +import { LayoutComponents } from '@/components/LayoutComponents'; +import { CodeBlock } from '@/components/CodeBlock'; + +# Components + +## Layout Components + +We built a couple of layout components to help you build responsive elements +that will be consistent with the rest of your Backstage instance. These +components are opinionated and use TypeScript to ensure that the props you +provide are the ones coming from the theme. + + + Hello World + + Project 1 + Project 2 + + +`} +/> + + + +## Components + +### Actions + + + + + + + + +### Content display + + + + + + +### Selection and inputs + + + + + + + + + + + +### Navigation + + + + + + + + +### Images and icons + + + + + + +### Feedback indicators + + + + + + +### Typography + + + + diff --git a/docs-ui/src/app/install/page.mdx b/docs-ui/src/app/install/page.mdx deleted file mode 100644 index cf14691441..0000000000 --- a/docs-ui/src/app/install/page.mdx +++ /dev/null @@ -1,52 +0,0 @@ -import { CodeBlock } from '@/components/CodeBlock'; -import { Banner } from '@/components/Banner'; -import { snippet } from './snippets'; - -# How to install Backstage UI - -## How to import BUI's global styles - -Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. -First, you'll need to install the package using a package manager. For example, if you're using Yarn: - - - -);`} -/> - - - -## How to use BUI components - -As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles -again in your plugin as this will be handled at the root of your application. To get started, just add the library to -your plugin and import the components you need. - - - - - -## Next steps - -Now that you have the basics down, you can start building your plugin using the new design system. -Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system. -If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2). diff --git a/docs-ui/src/app/layout.module.css b/docs-ui/src/app/layout.module.css index 7399fc2485..df89f02283 100644 --- a/docs-ui/src/app/layout.module.css +++ b/docs-ui/src/app/layout.module.css @@ -1,28 +1,46 @@ .container { position: relative; z-index: 20; - width: calc(100% - 64px); - margin-inline: 0 16px; - margin-left: 32px; - margin-right: 32px; - margin-top: 112px; margin-bottom: 48px; - padding-inline: 40px; - padding-block: 48px; - background-color: var(--panel); - border-radius: 0.5rem; + padding-inline: 24px; +} + +.contentWrapper { + display: flex; + width: 100%; + max-width: 1200px; + margin: 0 auto; + justify-content: center; + flex-direction: column; } .content { - width: 100%; - max-width: 960px; - margin: 0 auto; + display: flex; + gap: 40px; +} + +.contentInner { + flex: 1; + min-width: 0; + overflow-x: hidden; +} + +.toc { + display: none; } @media (min-width: 768px) { .container { - width: calc(100% - 332px - 40px); - margin-left: 332px; + width: calc(100% - 260px); + margin-left: 260px; margin-right: 40px; } } + +@media (min-width: 1280px) { + .toc { + display: block; + width: 240px; + flex-shrink: 0; + } +} diff --git a/docs-ui/src/app/layout.tsx b/docs-ui/src/app/layout.tsx index d197112431..ad056626d8 100644 --- a/docs-ui/src/app/layout.tsx +++ b/docs-ui/src/app/layout.tsx @@ -1,9 +1,10 @@ import type { Metadata } from 'next'; -import { Sidebar } from '../components/Sidebar'; +import { Sidebar } from '@/components/Sidebar/Sidebar'; import { Toolbar } from '@/components/Toolbar'; -import { StickyHeader } from '../components/StickyHeader/StickyHeader'; import { Providers } from './providers'; import { CustomTheme } from '@/components/CustomTheme'; +import { TableOfContents } from '@/components/TableOfContents'; +import { MobileBottomNav } from '@/components/MobileBottomNav'; import styles from './layout.module.css'; import '../css/globals.css'; @@ -51,11 +52,18 @@ export default async function RootLayout({ - -
-
{children}
+
+ +
+
{children}
+ +
+
+
diff --git a/docs-ui/src/app/layout/page.mdx b/docs-ui/src/app/layout/page.mdx deleted file mode 100644 index 3c3b998490..0000000000 --- a/docs-ui/src/app/layout/page.mdx +++ /dev/null @@ -1,42 +0,0 @@ -import { LayoutComponents } from '@/components/LayoutComponents'; -import { CodeBlock } from '@/components/CodeBlock'; - -# Layout - -Backstage UI is made for extensibility. We built this library to make it easy for any -Backstage plugin creator to be able to build their ideas at speed ensuring -consistency across the rest of your ecosystem. Each component is designed to -be editable to match your need but sometimes you want to have more control -over the layout of your page. To help you with that, we created a set of -layout components that you can use to build your own layouts. All of these -components are built to extend on our theming system, making it easy for you -to build your own layouts. Sometimes these components are not enough so we -created a set of helpers to be used with any CSS-in-JS library. - -## Layout Components - -We built a couple of layout components to help you build responsive elements -that will be consistent with the rest of your Backstage instance. These -components are opinionated and use TypeScript to ensure that the props you -provide are the ones coming from the theme. - - - Hello World - - Project 1 - Project 2 - - -`} -/> - - - -## Layout Helpers - -Sometimes you want to use global tokens dynamically outside of React -components. To help you with that we would like to provide a set of helpers -that you can use in your code. These helpers are not available just yet but we -are working on it. diff --git a/docs-ui/src/app/page.mdx b/docs-ui/src/app/page.mdx index ffd3bc9dcb..9a796b401b 100644 --- a/docs-ui/src/app/page.mdx +++ b/docs-ui/src/app/page.mdx @@ -1,179 +1,83 @@ -import { ComponentCards, ComponentCard } from '@/components/ComponentCards'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Banner } from '@/components/Banner'; +import { snippet } from './snippets'; -## Welcome to Backstage UI, the new design library for Backstage plugins. +# Welcome to Backstage UI -This project is still under active development but we will make sure to document -the API as we go. We are aiming to improve the general UI of Backstage and -plugins across Backstage. This new library will take time to build but we are -building it incrementally with not conflict with the existing theming system. +Backstage UI is a design system created specifically for Backstage, built with React, TypeScript, and vanilla CSS. +This open-source library is hosted in the Backstage monorepo. While it can be used in other projects, Backstage UI +is designed to deliver a consistent, accessible, and extensible experience tailored to Backstage users. -### Actions +## Import BUI's global styles - - - - - - +Backstage UI works by importing a global CSS file at the root of your application. This file includes all the default styles for the components. +First, you'll need to install the package using a package manager. For example, if you're using Yarn: -### Layout + - - - - - - - - +);`} +/> - - - - - - - - - + -### Navigation +## Use BUI components - - - - - - +As a plugin maintainer, you can use BUI components in your plugin. As mentioned above, you should not import the styles +again in your plugin as this will be handled at the root of your application. To get started, just add the library to +your plugin and import the components you need. -### Images and icons + - - - - - -### Feedback indicators - - - - - - -### Typography - - - - + ## Support Now that you have the basics down, you can start building your plugin using the new design system. Please familiarise yourself first with our theming principles. This will help you understand the core concepts of the design system. If you have any questions, please reach out to us on [Discord](https://discord.gg/MUpMjP2). + +## Philosophy + +Backstage empowers product teams to build software faster and with greater quality. Its extensibility, +however, required us to rethink how to deliver a consistent and accessible user experience. Our goal is +to enable plugin creators to design plugins that seamlessly integrate with Backstage's look and feel while +still allowing customization to match individual brands. + +Instead of reinventing the wheel, we chose to focus on layout and styling while leveraging existing headless +component libraries for functionality. This approach allows us to dedicate our efforts to creating a cohesive +and flexible theming system. + +## Team + +Backstage UI is designed and maintained primarily by Spotify's Backstage team, leveraging Spotify's expertise in +crafting high-quality design and technology. Drawing from our experience in building reliable and intuitive +user experiences for the music industry, we've created a design system that looks great and works seamlessly. + +## Community + +Backstage UI is an open-source project and we welcome contributions from the community. If you are interested in +contributing to Backstage UI, please read our [contributing guide](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) +and our [code of conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md). + +## License + +Backstage UI is licensed under the Apache 2.0 license. See the [LICENSE](https://github.com/backstage/backstage/blob/master/LICENSE) file for more details. diff --git a/docs-ui/src/app/responsive/page.mdx b/docs-ui/src/app/responsive/page.mdx deleted file mode 100644 index 9d37b1329f..0000000000 --- a/docs-ui/src/app/responsive/page.mdx +++ /dev/null @@ -1,107 +0,0 @@ -import * as Table from '@/components/Table'; -import { Chip } from '@/components/Chip'; -import { CodeBlock } from '@/components/CodeBlock'; - -# Responsive - -Backstage UI is built on a responsive design system, meaning that the components are -designed to adapt to different screen sizes. By default we offer a set of -breakpoints that you can use to create responsive components. - -## Breakpoints - - - - - Breakpoint prefix - Minimum width - CSS - - - - - - xs - - - 0px - - - {`{ ... }`} - - - - - sm - - - 640px - - - {`@media (min-width: 640px) { ... }`} - - - - - md - - - 768px - - - {`@media (min-width: 768px) { ... }`} - - - - - lg - - - 1024px - - - {`@media (min-width: 1024px) { ... }`} - - - - - xl - - - 1280px - - - {`@media (min-width: 1280px) { ... }`} - - - - - 2xl - - - 1536px - - - {`@media (min-width: 1536px) { ... }`} - - - - - -## Responsive components - -Backstage UI components are designed to be responsive, meaning that they will adapt -to different screen sizes. Not every component is responsive, but the ones -that are will have a prop to control the responsive behavior. - -The behaviour is the same for each component. For each prop, instead of adding -the value, you add an object with the value and the breakpoint prefix. - -Button - -// Responsive value - -`} /> diff --git a/docs-ui/src/app/install/snippets.ts b/docs-ui/src/app/snippets.ts similarity index 100% rename from docs-ui/src/app/install/snippets.ts rename to docs-ui/src/app/snippets.ts diff --git a/docs-ui/src/app/theming/page.mdx b/docs-ui/src/app/tokens/page.mdx similarity index 86% rename from docs-ui/src/app/theming/page.mdx rename to docs-ui/src/app/tokens/page.mdx index 455b2cd7f5..8e9031c81c 100644 --- a/docs-ui/src/app/theming/page.mdx +++ b/docs-ui/src/app/tokens/page.mdx @@ -3,48 +3,109 @@ import * as Table from '@/components/Table'; import { Chip } from '@/components/Chip'; import { customTheme } from '@/snippets/code-snippets'; -# Theming +# Tokens -Backstage UI theming is built entirely on CSS, without relying on any CSS-in-JS libraries. -At its core, it provides a solid default theme that is easily customizable using a -comprehensive set of CSS variables. Additionally, it enables anyone to adapt the design -to their specific needs. Each component comes with fixed class names, making customization -even more straightforward. +## Responsive breakpoints -## Light & Dark modes +Backstage UI is built on a responsive design system, meaning that the components are +designed to adapt to different screen sizes. By default we offer a set of +breakpoints that you can use to create responsive components. -By default, Backstage UI supports both light and dark modes using the `data-theme-mode` attribute. -The light theme is applied by default if no `data-theme-mode` attribute is specified. To create -a custom theme, you'll need to define both light and dark modes as outlined below. If -only one mode is defined, the other will fall back to the default theme. + + + + Breakpoint prefix + Minimum width + CSS + + + + + + xs + + + 0px + + + {`{ ... }`} + + + + + sm + + + 640px + + + {`@media (min-width: 640px) { ... }`} + + + + + md + + + 768px + + + {`@media (min-width: 768px) { ... }`} + + + + + lg + + + 1024px + + + {`@media (min-width: 1024px) { ... }`} + + + + + xl + + + 1280px + + + {`@media (min-width: 1280px) { ... }`} + + + + + 2xl + + + 1536px + + + {`@media (min-width: 1536px) { ... }`} + + + + -## How to create your own theme +Backstage UI components are designed to be responsive, meaning that they will adapt +to different screen sizes. Not every component is responsive, but the ones +that are will have a prop to control the responsive behavior. -In our [started guide](/), we ask you to import two css files. The `core.css` file includes -the default set of variables. We recommend to keep this file in place and add your own theme -on top of it. `core.css` also include an opinionated reset. If you decided to remove `core.css` -you will have to provide your own reset css. +The behaviour is the same for each component. For each prop, instead of adding +the value, you add an object with the value and the breakpoint prefix. -Here's an example of how your theme.css file should look like: + + -## CSS class name structure +// Responsive value -All Backstage UI components come with a set of CSS classes that you can use to style them. To make it -easier to identify the class name you can use, we use a specific structure for the class names. +`} /> - - -Every component has a unique prefix `.bui-` followed by the component name. Component props -are represented using the `data-` attribute. That way, class names are easily identifiable. - -## Available CSS variables - -### Base colors +## Base colors These colors are used for special purposes like ring, scrollbar, ... @@ -123,7 +184,7 @@ These colors are used for special purposes like ring, scrollbar, ... -### Core background colors +## 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 @@ -224,7 +285,7 @@ color of your app. -### Foreground colors +## Foreground colors Foreground colours are meant to work in pair with a background colours. Typically this would work for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors @@ -329,7 +390,7 @@ are prefixed with `fg` to make it easier to identify. -### Border colors +## Border colors These border colors are mostly meant to be used as borders on top of any components with low contrast to help as a separator with the different background colors. @@ -391,7 +452,7 @@ low contrast to help as a separator with the different background colors. -### Special colors +## Special colors These colors are used for special purposes like ring, scrollbar, ... @@ -424,7 +485,7 @@ These colors are used for special purposes like ring, scrollbar, ... -### Font families +## Font families We have two fonts that we use across Backstage UI. The first one is the sans-serif font that we use for the body of the application. The second one is the @@ -453,7 +514,7 @@ monospace font that we use for code blocks and tables. -### Font weights +## Font weights We have two font weights that we use across Backstage UI. Regular or Bold. @@ -480,7 +541,7 @@ We have two font weights that we use across Backstage UI. Regular or Bold. -### Spacing +## Spacing We built a spacing system based on a single value `--bui-space`. This value is used to calculate the spacing for all the components. By default if you would like to @@ -619,7 +680,7 @@ tokens for pretty much each spacing properties like padding, margin, gaps, ... -### Radius +## Radius We use a radius system to make sure that the components have a consistent look and feel. diff --git a/docs-ui/src/components/Changelog/index.tsx b/docs-ui/src/components/Changelog/index.tsx index 224e23d668..d7a547b901 100644 --- a/docs-ui/src/components/Changelog/index.tsx +++ b/docs-ui/src/components/Changelog/index.tsx @@ -52,14 +52,15 @@ export function Changelog() { ${bumpEntries .map(e => { const prs = - e.prs.length > 0 && - e.prs - .map( - pr => - `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, - ) - .join(', '); - return `- ${e.description} ${prs}`; + e.prs.length > 0 + ? e.prs + .map( + pr => + `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, + ) + .join(', ') + : ''; + return `- ${e.description}${prs ? ` ${prs}` : ''}`; }) .join('\n')}`; }) diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx index f8832b0731..ff86433268 100644 --- a/docs-ui/src/components/ChangelogComponent/index.tsx +++ b/docs-ui/src/components/ChangelogComponent/index.tsx @@ -17,14 +17,17 @@ export const ChangelogComponent = ({ component }: { component: Component }) => { ${componentChangelog ?.map(change => { const prs = - change.prs.length > 0 && - change.prs - .map( - pr => - `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, - ) - .join(', '); - return `- \`${change.version}\` - ${change.description} ${prs}`; + change.prs.length > 0 + ? change.prs + .map( + pr => + `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, + ) + .join(', ') + : ''; + return `- \`${change.version}\` - ${change.description}${ + prs ? ` ${prs}` : '' + }`; }) .join('\n')}`} /> diff --git a/docs-ui/src/components/CodeBlock/index.tsx b/docs-ui/src/components/CodeBlock/index.tsx index 96fdb6bea5..1467bca465 100644 --- a/docs-ui/src/components/CodeBlock/index.tsx +++ b/docs-ui/src/components/CodeBlock/index.tsx @@ -9,15 +9,24 @@ export interface CodeBlockProps { code?: string; } -export async function CodeBlock({ lang = 'tsx', title, code }: CodeBlockProps) { - const out = await codeToHtml(code || '', { - lang: lang, +export async function CodeBlock(props: CodeBlockProps) { + const { lang = 'tsx', title, code } = props; + let out = await codeToHtml(code || '', { + lang, + transformers: [transformerNotationDiff({ matchAlgorithm: 'v3' })], themes: { - light: 'min-light', + light: 'github-dark', dark: 'min-dark', }, - transformers: [transformerNotationDiff({ matchAlgorithm: 'v3' })], }); + // Remove background-color from the pre tag to use our theme colors + out = out.replace( + /style="([^"]*?)background-color:[^;]+;?([^"]*?)"/g, + 'style="$1$2"', + ); + // Clean up empty style attributes + out = out.replace(/style=""\s?/g, ''); + return ; } diff --git a/docs-ui/src/components/CodeBlock/styles.module.css b/docs-ui/src/components/CodeBlock/styles.module.css index d786140482..30e1837f60 100644 --- a/docs-ui/src/components/CodeBlock/styles.module.css +++ b/docs-ui/src/components/CodeBlock/styles.module.css @@ -1,33 +1,29 @@ .codeBlock { - border-radius: 4px; - border: 1px solid var(--border); + border-radius: 8px; position: relative; background: transparent; overflow-x: auto; font-family: var(--font-mono); - background-color: #fff; + background-color: var(--code-bg); margin-bottom: 1rem; } -[data-theme-mode='dark'] .codeBlock { - background-color: #121212; -} - .title { - border-bottom: 1px solid var(--border); padding: 12px 20px; font-size: 0.8125rem; - color: var(--primary); + color: #fff; font-family: var(--font-regular); font-weight: var(--font-weight-bold); + background-color: var(--code-title); } .title code { - background-color: var(--surface-1); + background-color: rgba(255, 255, 255, 0.1); padding: 0.2rem 0.375rem; border-radius: 0.25rem; - color: var(--secondary); - font-size: 0.8125rem; + color: #fff; + font-size: 0.75rem; + font-weight: var(--font-weight-bold); } .code { diff --git a/docs-ui/src/components/ComponentCards/ComponentCards.module.css b/docs-ui/src/components/ComponentCards/ComponentCards.module.css index 688010110c..8eb9dbd3d0 100644 --- a/docs-ui/src/components/ComponentCards/ComponentCards.module.css +++ b/docs-ui/src/components/ComponentCards/ComponentCards.module.css @@ -28,7 +28,7 @@ display: flex; flex-direction: column; justify-content: flex-end; - background-color: var(--panel); + background-color: var(--bg); border-radius: 8px; border: 1px solid var(--border); padding: 16px; @@ -36,7 +36,7 @@ min-height: 120px; &:hover { - background-color: var(--panel-hover); + background-color: var(--bg-hover); } } diff --git a/docs-ui/src/components/CustomTheme/styles.module.css b/docs-ui/src/components/CustomTheme/styles.module.css index 05604c48be..dddd3ff7e4 100644 --- a/docs-ui/src/components/CustomTheme/styles.module.css +++ b/docs-ui/src/components/CustomTheme/styles.module.css @@ -5,7 +5,7 @@ right: 16px; width: 240px; height: 47px; - background-color: var(--panel); + background-color: var(--bg); border-radius: 0.375rem; border: 1px solid var(--border); display: flex; @@ -33,7 +33,7 @@ height: 46px; flex-shrink: 0; border-bottom: 1px solid var(--border); - background-color: var(--panel); + background-color: var(--bg); display: flex; justify-content: space-between; align-items: center; diff --git a/docs-ui/src/components/HeadlessBanners/styles.module.css b/docs-ui/src/components/HeadlessBanners/styles.module.css index 384327c394..c0066eccff 100644 --- a/docs-ui/src/components/HeadlessBanners/styles.module.css +++ b/docs-ui/src/components/HeadlessBanners/styles.module.css @@ -1,6 +1,6 @@ .container { display: flex; - background-color: var(--panel); + background-color: var(--bg); padding: 1.5rem; border-radius: 0.25rem; border: 1px solid var(--border); diff --git a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css b/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css index a09a04fb5c..9009cdb6a4 100644 --- a/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css +++ b/docs-ui/src/components/LayoutComponents/LayoutComponents.module.css @@ -19,7 +19,7 @@ & .content { flex: none; - background-color: var(--panel); + background-color: var(--bg); border: 1px solid var(--border); border-radius: 4px; width: 100%; diff --git a/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.module.css b/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.module.css new file mode 100644 index 0000000000..632f005b65 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.module.css @@ -0,0 +1,40 @@ +.icon { + display: block; +} + +.topLine, +.middleLine, +.bottomLine { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Menu state (default) */ +.topLine { + transform-origin: 10px 5px; + transform: rotate(0deg); +} + +.middleLine { + opacity: 1; + transform-origin: 10px 10px; +} + +.bottomLine { + transform-origin: 10px 15px; + transform: rotate(0deg); +} + +/* Close state (when open) */ +.icon[data-open='true'] .topLine { + transform-origin: 10px 5px; + transform: translateY(5px) rotate(45deg); +} + +.icon[data-open='true'] .middleLine { + opacity: 0; +} + +.icon[data-open='true'] .bottomLine { + transform-origin: 10px 15px; + transform: translateY(-5px) rotate(-45deg); +} diff --git a/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.tsx b/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.tsx new file mode 100644 index 0000000000..80edd64d43 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/AnimatedMenuIcon.tsx @@ -0,0 +1,56 @@ +import styles from './AnimatedMenuIcon.module.css'; + +interface AnimatedMenuIconProps { + isOpen: boolean; + size?: number; +} + +export const AnimatedMenuIcon = ({ + isOpen, + size = 20, +}: AnimatedMenuIconProps) => { + return ( + + + + + + + + ); +}; diff --git a/docs-ui/src/components/MobileBottomNav/MobileBottomNav.module.css b/docs-ui/src/components/MobileBottomNav/MobileBottomNav.module.css new file mode 100644 index 0000000000..9c128c3959 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/MobileBottomNav.module.css @@ -0,0 +1,205 @@ +/* Mobile Bottom Navigation Island */ +.mobileBottomNav { + display: block; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 16px; + z-index: 300; + pointer-events: none; + + @media (min-width: 768px) { + display: none; + } +} + +.island { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin: 0 auto; + width: 50vw; + padding: 8px 12px 8px 12px; + border-radius: 32px; + pointer-events: auto; + background-color: #000; +} + +:global([data-theme-mode='dark']) .island { + background-color: #fff; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3); +} + +.menuButton { + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 50%; + transition: background-color 0.2s ease-in-out; + + /* White icons on light mode (black island) */ + color: white; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } +} + +:global([data-theme-mode='dark']) .menuButton { + /* Black icons on dark mode (white island) */ + color: black; + + &:hover { + background-color: rgba(0, 0, 0, 0.1); + } +} + +.buttonGroup { + display: flex; + align-items: center; + gap: 4px; + padding: 4px; + border-radius: 24px; + background-color: rgba(255, 255, 255, 0.2); +} + +:global([data-theme-mode='dark']) .buttonGroup { + background-color: rgba(0, 0, 0, 0.08); +} + +.buttonGroup button { + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + cursor: pointer; + padding: 6px 12px; + border-radius: 20px; + transition: background-color 0.2s ease-in-out; + + /* White icons on light mode */ + color: white; + + &[data-selected] { + background-color: rgba(255, 255, 255, 0.2); + } + + &:hover { + background-color: rgba(255, 255, 255, 0.15); + } +} + +:global([data-theme-mode='dark']) .buttonGroup button { + /* Black icons on dark mode */ + color: black; + + &[data-selected] { + background-color: rgba(0, 0, 0, 0.2); + } + + &:hover { + background-color: rgba(0, 0, 0, 0.15); + } +} + +/* Mobile Menu Bottom Sheet */ +.overlay { + position: fixed; + inset: 0; + z-index: 200; + background-color: rgba(38, 38, 38, 0.7); + display: flex; + align-items: flex-end; + justify-content: center; + + &[data-entering] { + animation: overlayFadeIn 200ms ease-out; + } + + &[data-exiting] { + animation: overlayFadeOut 200ms ease-in; + } +} + +@keyframes overlayFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes overlayFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.modal { + width: calc(100% - 48px); + max-height: 50vh; + background-color: var(--bg); + border-radius: 16px; + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + outline: none; + margin-bottom: 96px; + + &[data-entering] { + animation: slideUp 300ms cubic-bezier(0.32, 0.72, 0, 1); + } + + &[data-exiting] { + animation: slideDown 200ms cubic-bezier(0.32, 0.72, 0, 1); + } +} + +@keyframes slideUp { + from { + transform: translateY(8%); + } + to { + transform: translateY(0); + } +} + +@keyframes slideDown { + from { + transform: translateY(0); + } + to { + transform: translateY(8%); + } +} + +.dialog { + display: flex; + flex-direction: column; + height: 100%; + max-height: 70vh; + outline: none; + overflow: hidden; +} + +.menuContent { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 16px; + display: flex; + flex-direction: column; + gap: 2px; + min-height: 0; +} diff --git a/docs-ui/src/components/MobileBottomNav/MobileBottomNav.tsx b/docs-ui/src/components/MobileBottomNav/MobileBottomNav.tsx new file mode 100644 index 0000000000..52ec2ee9d9 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/MobileBottomNav.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { useState } from 'react'; +import { RiMoonLine, RiSunLine } from '@remixicon/react'; +import { Button, ToggleButton, ToggleButtonGroup } from 'react-aria-components'; +import styles from './MobileBottomNav.module.css'; +import { usePlayground } from '@/utils/playground-context'; +import { MobileMenu } from './MobileMenu'; +import { AnimatedMenuIcon } from './AnimatedMenuIcon'; + +export const MobileBottomNav = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const { selectedTheme, setSelectedTheme } = usePlayground(); + + return ( + <> +
+
+ + + + + + + + + +
+
+ setIsMenuOpen(false)} /> + + ); +}; diff --git a/docs-ui/src/components/MobileBottomNav/MobileMenu.tsx b/docs-ui/src/components/MobileBottomNav/MobileMenu.tsx new file mode 100644 index 0000000000..3a3a485aa5 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/MobileMenu.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { Dialog, Modal, ModalOverlay } from 'react-aria-components'; +import styles from './MobileBottomNav.module.css'; +import { Navigation } from '@/components/Navigation'; + +interface MobileMenuProps { + isOpen: boolean; + onClose: () => void; +} + +export const MobileMenu = ({ isOpen, onClose }: MobileMenuProps) => { + return ( + + + + {({ close }) => ( +
+ +
+ )} +
+
+
+ ); +}; diff --git a/docs-ui/src/components/MobileBottomNav/index.tsx b/docs-ui/src/components/MobileBottomNav/index.tsx new file mode 100644 index 0000000000..4b7449c9c5 --- /dev/null +++ b/docs-ui/src/components/MobileBottomNav/index.tsx @@ -0,0 +1 @@ +export { MobileBottomNav } from './MobileBottomNav'; diff --git a/docs-ui/src/components/Navigation/Navigation.module.css b/docs-ui/src/components/Navigation/Navigation.module.css new file mode 100644 index 0000000000..f5e1312ab1 --- /dev/null +++ b/docs-ui/src/components/Navigation/Navigation.module.css @@ -0,0 +1,100 @@ +.topNav { + & ul { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 2px; + } + + & li { + margin: 0; + padding: 0; + list-style: none; + } + + & li div, + & li a { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 4px; + cursor: pointer; + color: var(--primary); + text-decoration: none; + + &:hover { + background-color: var(--action); + + &[data-disabled='true'] { + background-color: transparent; + } + } + + &[data-active='true'] { + background-color: var(--action); + + &[data-disabled='true'] { + background-color: transparent; + } + } + + &[data-disabled='true'] { + opacity: 0.5; + cursor: not-allowed; + } + } +} + +.sectionTitle { + font-size: 0.6875rem; + font-weight: 500; + padding: 0 12px 4px; + color: var(--secondary); + margin-top: 40px; + text-transform: uppercase; + + &:first-child { + margin-top: 12px; + } +} + +.line { + text-decoration: none; + align-items: center; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 28px; + padding: 0 12px; + border-radius: 4px; + color: var(--primary); + flex-shrink: 0; + + &:hover { + background-color: var(--action); + } + + &.active { + background-color: var(--action); + } + + &.active .lineTitle { + color: var(--primary); + } +} + +.lineTitle { + font-size: 14px; + font-weight: 400; + color: var(--primary); +} + +.lineStatus { + font-size: 14px; + color: var(--secondary); +} diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx new file mode 100644 index 0000000000..13fa87661f --- /dev/null +++ b/docs-ui/src/components/Navigation/Navigation.tsx @@ -0,0 +1,125 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { Fragment } from 'react'; +import clsx from 'clsx'; +import { + RiCollageLine, + RiFileHistoryLine, + RiHazeLine, + RiPaletteLine, + RiServiceLine, + RiStackLine, +} from '@remixicon/react'; +import { components, layoutComponents } from '@/utils/data'; +import styles from './Navigation.module.css'; + +interface NavigationProps { + onLinkClick?: () => void; +} + +const data = [ + { + title: 'Layout Components', + content: layoutComponents, + url: '/components', + }, + { + title: 'Components', + content: components, + url: '/components', + }, +]; + +export const Navigation = ({ onLinkClick }: NavigationProps) => { + const pathname = usePathname(); + + return ( + <> + + {data.map(section => { + return ( + +
{section.title}
+ + {section.content.map(item => { + const isActive = pathname === `${section.url}/${item.slug}`; + + return ( + +
{item.title}
+
+ {item.status === 'alpha' && 'Alpha'} + {item.status === 'beta' && 'Beta'} + {item.status === 'inProgress' && 'In Progress'} + {item.status === 'stable' && 'Stable'} + {item.status === 'deprecated' && 'Deprecated'} +
+ + ); + })} +
+ ); + })} + + ); +}; diff --git a/docs-ui/src/components/Navigation/index.tsx b/docs-ui/src/components/Navigation/index.tsx new file mode 100644 index 0000000000..61f39d15c9 --- /dev/null +++ b/docs-ui/src/components/Navigation/index.tsx @@ -0,0 +1 @@ +export { Navigation } from './Navigation'; diff --git a/docs-ui/src/components/PageTitle/index.tsx b/docs-ui/src/components/PageTitle/index.tsx index 540e19eb36..c43ff75fc7 100644 --- a/docs-ui/src/components/PageTitle/index.tsx +++ b/docs-ui/src/components/PageTitle/index.tsx @@ -5,15 +5,12 @@ import styles from './PageTitle.module.css'; export const PageTitle = ({ title, description, - type = 'component', }: { title: string; - type?: string; description: string; }) => { return (
-
{type}
{ + return ( +
+
+ +
+ + +
+
+ +
+
+
+ + + +
+
+ ); +}; diff --git a/docs-ui/src/components/Sidebar/index.tsx b/docs-ui/src/components/Sidebar/index.tsx deleted file mode 100644 index 59e297e26e..0000000000 --- a/docs-ui/src/components/Sidebar/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; - -import styles from './Sidebar.module.css'; -import { - components, - overview, - layoutComponents, - coreConcepts, -} from '@/utils/data'; -import { ScrollArea } from '@base-ui-components/react/scroll-area'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { motion } from 'motion/react'; -import { Fragment } from 'react'; - -const data = [ - { - title: 'Overview', - content: overview, - url: '', - }, - { - title: 'Core Concepts', - content: coreConcepts, - url: '', - }, - { - title: 'Layout Components', - content: layoutComponents, - url: '/components', - }, - { - title: 'Components', - content: components, - url: '/components', - }, -]; - -export const Sidebar = () => { - const pathname = usePathname(); - const isPlayground = pathname.includes('/playground'); - - return ( -
- - -
-
- - {data.map(section => { - return ( - -
{section.title}
- - {section.content.map(item => { - const isActive = - pathname === `${section.url}/${item.slug}`; - - return ( - -
{item.title}
-
- {item.status === 'alpha' && 'Alpha'} - {item.status === 'beta' && 'Beta'} - {item.status === 'inProgress' && 'In Progress'} - {item.status === 'stable' && 'Stable'} - {item.status === 'deprecated' && 'Deprecated'} -
- - ); - })} -
- ); - })} -
-
-
-
- - - -
-
- ); -}; diff --git a/docs-ui/src/components/Snippet/styles.module.css b/docs-ui/src/components/Snippet/styles.module.css index bb0d1cbc50..3141564293 100644 --- a/docs-ui/src/components/Snippet/styles.module.css +++ b/docs-ui/src/components/Snippet/styles.module.css @@ -5,9 +5,9 @@ } .preview { - border-radius: 4px; + border-radius: 8px; box-shadow: inset 0 0 0 1px var(--border); - background-color: var(--bg); + background-color: var(--bui-bg); padding: 1px; position: relative; } diff --git a/docs-ui/src/components/StickyHeader/StickyHeader.module.css b/docs-ui/src/components/StickyHeader/StickyHeader.module.css deleted file mode 100644 index 7cfc197f83..0000000000 --- a/docs-ui/src/components/StickyHeader/StickyHeader.module.css +++ /dev/null @@ -1,98 +0,0 @@ -.stickyHeader { - position: fixed; - top: 0px; - left: 32px; - right: 32px; - z-index: 99999; - background-color: var(--background); - padding: 16px 20px 16px 40px; - display: flex; - justify-content: space-between; - align-items: center; - backdrop-filter: blur(10px); - pointer-events: auto; - isolation: isolate; - transform: translateZ(0); - opacity: 0; - width: calc(100% - 64px); - mask-image: linear-gradient(to bottom, black 0%, black 80%, transparent 100%); - -webkit-mask-image: linear-gradient( - to bottom, - black 0%, - black 80%, - transparent 100% - ); -} - -@media (max-width: 768px) { - .stickyHeader { - display: none; - } -} - -.right { - display: flex; - align-items: center; - gap: 24px; -} - -.name { - font-size: 24px; - color: var(--text-primary); - font-weight: 400; -} - -.version { - font-size: 14px; - color: var(--text-secondary); - font-weight: 500; -} - -.actions { - display: flex; - align-items: center; - gap: 16px; -} - -.versionLinks { - display: flex; - align-items: center; - - a { - width: 48px; - height: 48px; - display: flex; - align-items: center; - justify-content: center; - color: var(--secondary); - transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; - border-radius: 48px; - - &:hover { - color: var(--primary); - background-color: var(--action); - } - } -} - -@media (max-width: 768px) { - .stickyHeader { - padding: 12px 16px; - } - - .right { - gap: 16px; - } - - .version { - font-size: 12px; - } -} - -@media (min-width: 768px) { - .stickyHeader { - width: calc(100% - 332px - 40px); - left: 332px; - right: 40px; - } -} diff --git a/docs-ui/src/components/StickyHeader/StickyHeader.tsx b/docs-ui/src/components/StickyHeader/StickyHeader.tsx deleted file mode 100644 index 804d759715..0000000000 --- a/docs-ui/src/components/StickyHeader/StickyHeader.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import { motion, useScroll, useTransform, circOut } from 'framer-motion'; -import { RiGithubLine, RiNpmjsLine } from '@remixicon/react'; -import { ThemeSelector } from '../Toolbar/theme'; -import { ThemeNameSelector } from '../Toolbar/theme-name'; -import { useCurrentPage } from '@/hooks/useCurrentPage'; -import styles from './StickyHeader.module.css'; - -export const StickyHeader = () => { - const { scrollY } = useScroll(); - const currentPage = useCurrentPage(); - - // Transform scroll position to opacity only - const opacity = useTransform(scrollY, [100, 200], [0, 1], { - clamp: false, - }); - - const yPos = useTransform(scrollY, [200, 500], [-60, 0], { - clamp: true, - ease: circOut, - }); - - return ( - -
{currentPage || 'Backstage UI'}
-
- - - -
-
- ); -}; diff --git a/docs-ui/src/components/Table/styles.module.css b/docs-ui/src/components/Table/styles.module.css index 5a5ebab450..8f38ac6979 100644 --- a/docs-ui/src/components/Table/styles.module.css +++ b/docs-ui/src/components/Table/styles.module.css @@ -17,7 +17,7 @@ padding: 12px 16px !important; border: none !important; text-align: left; - background-color: var(--panel) !important; + background-color: var(--bg) !important; font-size: 16px; & p { diff --git a/docs-ui/src/components/TableOfContents/TableOfContents.module.css b/docs-ui/src/components/TableOfContents/TableOfContents.module.css new file mode 100644 index 0000000000..7275c24520 --- /dev/null +++ b/docs-ui/src/components/TableOfContents/TableOfContents.module.css @@ -0,0 +1,93 @@ +.container { + position: sticky; + top: 24px; + max-height: calc(100vh - 48px); + overflow-y: auto; + padding: 16px 0; +} + +.list { + list-style: none; + padding: 0; + margin: 0; + position: relative; +} + +.list::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2px; + background-color: var(--border); +} + +.indicator { + position: absolute; + left: 0; + width: 2px; + background-color: var(--primary); + transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1), + height 0.3s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; + z-index: 1; +} + +.item { + position: relative; + margin: 0; +} + +.itemNested { + padding-left: 16px; +} + +.link { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + padding: 6px 16px; + font-size: 0.875rem; + color: var(--text-secondary); + cursor: pointer; + transition: color 0.2s ease; + line-height: 1.4; + position: relative; +} + +.link:hover { + color: var(--text); +} + +.itemActive .link { + color: var(--primary); + font-weight: 500; +} + +/* Hide scrollbar for webkit browsers */ +.container::-webkit-scrollbar { + width: 4px; +} + +.container::-webkit-scrollbar-track { + background: transparent; +} + +.container::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 2px; +} + +.container::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +/* Responsive: hide on smaller screens */ +@media (max-width: 1280px) { + .container { + display: none; + } +} diff --git a/docs-ui/src/components/TableOfContents/TableOfContents.tsx b/docs-ui/src/components/TableOfContents/TableOfContents.tsx new file mode 100644 index 0000000000..d18265415b --- /dev/null +++ b/docs-ui/src/components/TableOfContents/TableOfContents.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; +import styles from './TableOfContents.module.css'; + +interface Heading { + id: string; + text: string; + level: number; +} + +export function TableOfContents() { + const [headings, setHeadings] = useState([]); + const [activeId, setActiveId] = useState(''); + const [indicatorTop, setIndicatorTop] = useState(0); + const [indicatorHeight, setIndicatorHeight] = useState(0); + const pathname = usePathname(); + + useEffect(() => { + // Extract all H2 and H3 headings from the document + const elements = Array.from( + document.querySelectorAll('h2[id], h3[id]'), + ) as HTMLHeadingElement[]; + + const headingData: Heading[] = elements.map(element => ({ + id: element.id, + text: (element.textContent || '').replace('#', '').trim(), + level: parseInt(element.tagName.substring(1)), + })); + + setHeadings(headingData); + + // Set initial active heading (first visible heading or first heading) + if (headingData.length > 0) { + const viewportTop = window.scrollY + 100; // offset for header + const visibleHeading = elements.find(element => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY >= viewportTop - 200; + }); + setActiveId(visibleHeading?.id || headingData[0].id); + } + + // Set up IntersectionObserver to track visible headings + const observerOptions = { + rootMargin: '-80px 0px -80% 0px', + threshold: 1, + }; + + const observerCallback = (entries: IntersectionObserverEntry[]) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }; + + const observer = new IntersectionObserver( + observerCallback, + observerOptions, + ); + + elements.forEach(element => observer.observe(element)); + + return () => { + elements.forEach(element => observer.unobserve(element)); + }; + }, [pathname]); + + // Update indicator position when activeId changes + useEffect(() => { + if (activeId) { + const activeElement = document.querySelector( + `[data-toc-id="${activeId}"]`, + ) as HTMLElement; + if (activeElement) { + const list = activeElement.closest('ul'); + if (list) { + const listRect = list.getBoundingClientRect(); + const elementRect = activeElement.getBoundingClientRect(); + setIndicatorTop(elementRect.top - listRect.top); + setIndicatorHeight(elementRect.height); + } + } + } + }, [activeId, headings]); + + const handleClick = (id: string) => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + // Update URL without scrolling (already handled above) + window.history.pushState(null, '', `#${id}`); + } + }; + + if (headings.length === 0) { + return null; + } + + return ( + + ); +} diff --git a/docs-ui/src/components/TableOfContents/index.ts b/docs-ui/src/components/TableOfContents/index.ts new file mode 100644 index 0000000000..ffe4593ca7 --- /dev/null +++ b/docs-ui/src/components/TableOfContents/index.ts @@ -0,0 +1 @@ +export { TableOfContents } from './TableOfContents'; diff --git a/docs-ui/src/components/Theming/index.tsx b/docs-ui/src/components/Theming/index.tsx index fb669253a2..d06f51348b 100644 --- a/docs-ui/src/components/Theming/index.tsx +++ b/docs-ui/src/components/Theming/index.tsx @@ -1,18 +1,13 @@ import { MDXRemote } from 'next-mdx-remote-client/rsc'; import { formattedMDXComponents } from '@/mdx-components'; -import { Component } from '@/utils/changelog'; -import { componentDefinitions } from '../../../../packages/ui/src/utils/componentDefinitions'; -import type { DataAttributeValues } from '../../../../packages/ui/src/types'; +import type { + ComponentDefinition, + DataAttributeValues, +} from '../../../../packages/ui/src/types'; -export function Theming({ component }: { component: Component }) { - const componentDefinition = componentDefinitions[component]; - - if (!componentDefinition) { - return null; - } - - const classNames = componentDefinition.classNames; - const dataAttributes = componentDefinition.dataAttributes; +export function Theming({ definition }: { definition: ComponentDefinition }) { + const classNames = definition.classNames; + const dataAttributes = definition.dataAttributes; // Get the first class name const firstClassName = Object.values(classNames)[0]; diff --git a/docs-ui/src/components/Toolbar/Toolbar.module.css b/docs-ui/src/components/Toolbar/Toolbar.module.css index f0e58f2edb..581fc599b3 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.module.css +++ b/docs-ui/src/components/Toolbar/Toolbar.module.css @@ -1,76 +1,77 @@ .toolbar { - position: fixed; display: flex; - top: 0; - left: 32px; - right: 32px; - height: 112px; - z-index: 10; + height: 80px; align-items: center; justify-content: space-between; - padding-left: 0.25rem; -} + margin-bottom: 24px; -.left { - /* width: 296px; */ - display: flex; - align-items: center; - gap: 0.5rem; - padding-right: 20px; -} - -.right { - flex: 1; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.actions { - display: none; -} - -.version { - display: none; -} - -.versionLinks { - display: flex; - align-items: center; - - a { - width: 48px; - height: 48px; - display: flex; - align-items: center; - justify-content: center; - color: var(--secondary); - transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; - border-radius: 48px; - - &:hover { - color: var(--primary); - background-color: var(--action); - } - } -} - -@media (min-width: 600px) { - .actions { - display: flex; - align-items: center; - gap: 1rem; - } -} - -@media (min-width: 768px) { - .toolbar { + @media (min-width: 768px) { right: 40px; } } +.breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + font-weight: 500; +} + +.logoMobile { + display: block; + + @media (min-width: 768px) { + display: none; + } +} + +.breadcrumbDesktop { + display: none; + + @media (min-width: 768px) { + display: flex; + align-items: center; + gap: 0.5rem; + } +} + +.breadcrumbLink { + color: var(--secondary); + text-decoration: none; + cursor: pointer; + + &:hover { + color: var(--primary); + text-decoration: underline; + transition: color 0.2s ease-in-out; + text-decoration-thickness: 1px; + text-underline-offset: 4px; + } +} + +.breadcrumbSeparator { + color: var(--secondary); + flex-shrink: 0; +} + +.breadcrumbCurrent { + color: var(--primary); +} + +.actions { + display: none; + + @media (min-width: 320px) { + display: flex; + display: flex; + align-items: center; + gap: 0.5rem; + } +} + @media (min-width: 820px) { - .right { + .content { justify-content: space-between; } @@ -79,18 +80,164 @@ align-items: center; justify-content: center; /* background-color: var(--action); */ - border: 1px solid var(--border2); - border-radius: 48px; - padding-inline: 20px; - color: var(--primary); - font-size: 0.875rem; - font-weight: 500; - height: 48px; } } -@media (min-width: 960px) { - .left { - width: 296px; +.bubble { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: 32px; + padding-inline: 16px; + color: var(--primary); + font-size: 0.875rem; + font-weight: 500; + height: 32px; + cursor: pointer; + gap: 4px; + + &[data-selected] { + background-color: var(--action); + color: var(--primary); + } + + &:hover { + background-color: var(--action); + transition: background-color 0.2s ease-in-out; + } + + &[data-hide-tablet] { + display: none; + } + + @media (min-width: 1024px) { + &[data-hide-tablet] { + display: flex; + } + } +} + +.buttonGroup { + display: flex; + align-items: center; + gap: 4px; + border: 1px solid var(--border); + border-radius: 32px; + padding-inline: 4px; + height: 32px; + display: none; + + @media (min-width: 480px) { + display: flex; + } +} + +.buttonGroup button { + height: 24px; + background-color: var(--bg); + border: none; + border-radius: 28px; + padding-inline: 16px; + color: var(--primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + + &[data-selected] { + background-color: var(--action); + color: var(--primary); + } + + &:hover { + background-color: var(--action); + transition: background-color 0.2s ease-in-out; + } +} + +.Popup { + box-sizing: border-box; + padding-block: 0.25rem; + border-radius: 0.375rem; + background-color: var(--bg); + color: var(--color-gray-900); + border: 1px solid var(--border); + padding-inline: 0.25rem; + transform-origin: var(--transform-origin); + transition: transform 150ms, opacity 150ms; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scale(0.9); + } + + &[data-side='none'] { + transition: none; + transform: none; + opacity: 1; + } + + @media (prefers-color-scheme: light) { + outline: 1px solid var(--color-gray-200); + box-shadow: 0px 10px 15px -3px var(--color-gray-200), + 0px 4px 6px -4px var(--color-gray-200); + } + + @media (prefers-color-scheme: dark) { + outline: 1px solid var(--color-gray-300); + outline-offset: -1px; + } +} + +.Popup[data-trigger='Select'] { + min-width: var(--trigger-width); + + .ListBox { + display: block; + width: unset; + max-height: inherit; + min-height: unset; + border: none; + + .react-aria-Header { + padding-left: 1.571rem; + } + } + + .Item { + position: relative; + padding: 0 0.571rem 0 1.571rem; + height: 2rem; + display: flex; + align-items: center; + + &[data-focus-visible] { + outline: none; + } + + &[data-selected] { + font-weight: 600; + background: unset; + color: var(--text-color); + + &::before { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + left: 4px; + } + } + + &[data-focused], + &[data-pressed] { + background: var(--bg); + color: var(--primary); + cursor: pointer; + } } } diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index 29570739f8..e7defd5e8a 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -1,52 +1,168 @@ 'use client'; -import { RiGithubLine, RiNpmjsLine } from '@remixicon/react'; -import { motion, useScroll, useTransform } from 'framer-motion'; -import { useRef } from 'react'; -import { Logo } from './Logo'; -import { ThemeSelector } from './theme'; -import { ThemeNameSelector } from './theme-name'; +import { + RiArrowDownSLine, + RiArrowRightSLine, + RiGithubLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react'; +import { + Button, + ListBox, + ListBoxItem, + Popover, + Select, + SelectValue, + ToggleButton, + ToggleButtonGroup, +} from 'react-aria-components'; import styles from './Toolbar.module.css'; +import { usePlayground } from '@/utils/playground-context'; +import { usePathname } from 'next/navigation'; +import Link from 'next/link'; +import { components, layoutComponents } from '@/utils/data'; +import { Logo } from '@/components/Sidebar/Logo'; interface ToolbarProps { version: string; } -export const Toolbar = ({ version }: ToolbarProps) => { - const containerRef = useRef(null); - const { scrollY } = useScroll(); +const themes = [ + { name: 'Backstage', value: 'backstage' }, + { name: 'Spotify', value: 'spotify' }, + { name: 'Custom theme', value: 'custom' }, +]; - // Transform scroll velocity to vertical movement - const y = useTransform(scrollY, [0, 100], [0, -20], { - clamp: false, - }); +export const Toolbar = ({ version }: ToolbarProps) => { + const { + selectedTheme, + setSelectedTheme, + selectedThemeName, + setSelectedThemeName, + } = usePlayground(); + + const pathname = usePathname(); + + // Determine breadcrumb content based on current path + const getBreadcrumb = () => { + const allComponents = [...components, ...layoutComponents]; + + // Root page + if (pathname === '/') { + return { section: null, title: 'Getting Started' }; + } + + // Components index page + if (pathname === '/components') { + return { section: null, title: 'Components' }; + } + + // Component detail pages + if (pathname?.startsWith('/components/')) { + const slug = pathname.split('/components/')[1]; + const component = allComponents.find(c => c.slug === slug); + return { + section: 'Components', + sectionLink: '/components', + title: component?.title || slug, + }; + } + + // Tokens page + if (pathname === '/tokens') { + return { section: null, title: 'Tokens' }; + } + + // Changelog page + if (pathname === '/changelog') { + return { section: null, title: 'Changelog' }; + } + + return { section: null, title: '' }; + }; + + const breadcrumb = getBreadcrumb(); return ( -
-
- -
- -
Version {version} - Alpha
-
- - - +
+
+
+
- +
+ {breadcrumb.section && breadcrumb.sectionLink ? ( + <> + + {breadcrumb.section} + + + + {breadcrumb.title} + + + ) : ( + {breadcrumb.title} + )} +
+
+
+ + + Version {version} + + + + + + + + + + +
); }; diff --git a/docs-ui/src/components/Toolbar/theme-name.module.css b/docs-ui/src/components/Toolbar/theme-name.module.css deleted file mode 100644 index 1e1943b886..0000000000 --- a/docs-ui/src/components/Toolbar/theme-name.module.css +++ /dev/null @@ -1,110 +0,0 @@ -.Select { - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - height: 3rem; - padding-left: 1.25rem; - padding-right: 1rem; - margin: 0; - outline: 0; - border: 1px solid var(--color-gray-200); - border-radius: 3rem; - font-family: inherit; - font-size: 1rem; - line-height: 1.5rem; - color: var(--color-gray-900); - cursor: pointer; - user-select: none; - background-color: var(--action); - - &:focus-visible { - outline: 2px solid var(--color-blue); - outline-offset: -1px; - } -} - -.Popup { - box-sizing: border-box; - padding-block: 0.25rem; - border-radius: 0.375rem; - background-color: var(--panel); - color: var(--color-gray-900); - border: 1px solid var(--border); - padding-inline: 0.25rem; - transform-origin: var(--transform-origin); - transition: transform 150ms, opacity 150ms; - - &[data-starting-style], - &[data-ending-style] { - opacity: 0; - transform: scale(0.9); - } - - &[data-side='none'] { - transition: none; - transform: none; - opacity: 1; - } - - @media (prefers-color-scheme: light) { - outline: 1px solid var(--color-gray-200); - box-shadow: 0px 10px 15px -3px var(--color-gray-200), - 0px 4px 6px -4px var(--color-gray-200); - } - - @media (prefers-color-scheme: dark) { - outline: 1px solid var(--color-gray-300); - outline-offset: -1px; - } -} - -.Popup[data-trigger='Select'] { - min-width: var(--trigger-width); - - .ListBox { - display: block; - width: unset; - max-height: inherit; - min-height: unset; - border: none; - - .react-aria-Header { - padding-left: 1.571rem; - } - } - - .Item { - position: relative; - padding: 0 0.571rem 0 1.571rem; - height: 2rem; - display: flex; - align-items: center; - - &[data-focus-visible] { - outline: none; - } - - &[data-selected] { - font-weight: 600; - background: unset; - color: var(--text-color); - - &::before { - content: '✓'; - content: '✓' / ''; - alt: ' '; - position: absolute; - left: 4px; - } - } - - &[data-focused], - &[data-pressed] { - background: var(--bg); - color: var(--primary); - cursor: pointer; - } - } -} diff --git a/docs-ui/src/components/Toolbar/theme-name.tsx b/docs-ui/src/components/Toolbar/theme-name.tsx deleted file mode 100644 index ae9e7f9f35..0000000000 --- a/docs-ui/src/components/Toolbar/theme-name.tsx +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import { - Button, - ListBox, - ListBoxItem, - Popover, - Select, - SelectValue, -} from 'react-aria-components'; -import styles from './theme-name.module.css'; -import { usePlayground } from '@/utils/playground-context'; -import { RiArrowDownSLine } from '@remixicon/react'; - -const themes = [ - { name: 'Backstage', value: 'backstage' }, - { name: 'Spotify', value: 'spotify' }, - { name: 'Custom theme', value: 'custom' }, -]; - -export const ThemeNameSelector = () => { - const { selectedThemeName, setSelectedThemeName } = usePlayground(); - - return ( - - ); -}; diff --git a/docs-ui/src/components/Toolbar/theme.module.css b/docs-ui/src/components/Toolbar/theme.module.css deleted file mode 100644 index c5490a72d7..0000000000 --- a/docs-ui/src/components/Toolbar/theme.module.css +++ /dev/null @@ -1,79 +0,0 @@ -.tabs { - border-radius: 0.375rem; - width: 100%; -} - -.tabsTheme { - width: 100px; - border-radius: 0.375rem; -} - -.list { - display: flex; - position: relative; - z-index: 0; - gap: 0.25rem; -} - -.tab { - display: flex; - align-items: center; - justify-content: center; - border: 0; - margin: 0; - outline: 0; - background: none; - appearance: none; - color: var(--secondary); - user-select: none; - flex: 1; - cursor: pointer; - width: 3rem; - height: 3rem; - - &[data-selected] { - color: var(--primary); - - & p { - color: var(--primary); - } - } - - @media (hover: hover) { - &:hover { - color: var(--primary); - } - } - - &:focus-visible { - position: relative; - - &::before { - content: ''; - position: absolute; - inset: 0.25rem 0; - border-radius: 0.25rem; - outline: 2px solid var(--action); - outline-offset: -1px; - } - } -} - -.tab p { - color: var(--secondary) !important; -} - -.indicator { - position: absolute; - z-index: -1; - left: 0; - top: 50%; - translate: var(--active-tab-left) -50%; - width: var(--active-tab-width); - height: 3rem; - border-radius: 3rem; - background-color: var(--action); - transition-property: translate, width; - transition-duration: 200ms; - transition-timing-function: ease-in-out; -} diff --git a/docs-ui/src/components/Toolbar/theme.tsx b/docs-ui/src/components/Toolbar/theme.tsx deleted file mode 100644 index 7ece7bfe7b..0000000000 --- a/docs-ui/src/components/Toolbar/theme.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { Tabs } from '@base-ui-components/react/tabs'; -import { usePlayground } from '@/utils/playground-context'; -import styles from './theme.module.css'; -import { RiMoonLine, RiSunLine } from '@remixicon/react'; - -export const ThemeSelector = () => { - const { selectedTheme, setSelectedTheme } = usePlayground(); - - return ( - - - - - - - - - - ); -}; diff --git a/docs-ui/src/content/accordion.mdx b/docs-ui/src/content/accordion.mdx new file mode 100644 index 0000000000..fc4bad1f8e --- /dev/null +++ b/docs-ui/src/content/accordion.mdx @@ -0,0 +1,129 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { AccordionSnippet } from '@/snippets/stories-snippets'; +import { + accordionPropDefs, + accordionTriggerPropDefs, + accordionPanelPropDefs, + accordionGroupPropDefs, + accordionUsageSnippet, + accordionWithSubtitleSnippet, + accordionCustomTriggerSnippet, + accordionDefaultExpandedSnippet, + accordionGroupSingleOpenSnippet, + accordionGroupMultipleOpenSnippet, +} from './accordion.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { AccordionDefinition } from '../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={accordionUsageSnippet} +/> + +## Usage + + + +## API reference + +### Accordion + +Root container for the accordion. Renders a `
` element. + + + +### AccordionTrigger + +Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `

`, configurable via `level` prop) wrapping a ``; + export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/ui'; diff --git a/docs-ui/src/content/components/card.mdx b/docs-ui/src/content/card.mdx similarity index 95% rename from docs-ui/src/content/components/card.mdx rename to docs-ui/src/content/card.mdx index 06c3a5bc2e..c38a07e920 100644 --- a/docs-ui/src/content/components/card.mdx +++ b/docs-ui/src/content/card.mdx @@ -14,6 +14,7 @@ import { } from './card.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { CardDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/card.props.ts b/docs-ui/src/content/card.props.ts similarity index 100% rename from docs-ui/src/content/components/card.props.ts rename to docs-ui/src/content/card.props.ts diff --git a/docs-ui/src/content/components/checkbox.mdx b/docs-ui/src/content/checkbox.mdx similarity index 91% rename from docs-ui/src/content/components/checkbox.mdx rename to docs-ui/src/content/checkbox.mdx index 5770396ebb..c574e8fed4 100644 --- a/docs-ui/src/content/components/checkbox.mdx +++ b/docs-ui/src/content/checkbox.mdx @@ -10,6 +10,7 @@ import { } from './checkbox.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { CheckboxDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/checkbox.props.ts b/docs-ui/src/content/checkbox.props.ts similarity index 53% rename from docs-ui/src/content/components/checkbox.props.ts rename to docs-ui/src/content/checkbox.props.ts index c7ee69280e..ce0f1033ff 100644 --- a/docs-ui/src/content/components/checkbox.props.ts +++ b/docs-ui/src/content/checkbox.props.ts @@ -5,31 +5,32 @@ import { } from '@/utils/propDefs'; export const checkboxPropDefs: Record = { - label: { - type: 'string', - responsive: false, - }, - defaultChecked: { + children: { type: 'enum', - values: ['boolean', "'indeterminate'"], + values: ['React.ReactNode'], responsive: false, }, - checked: { - type: 'enum', - values: ['boolean', "'indeterminate'"], - responsive: false, - }, - onChange: { - type: 'enum', - values: ["(checked: boolean | 'indeterminate') => void"], - responsive: false, - }, - disabled: { + isSelected: { type: 'enum', values: ['boolean'], responsive: false, }, - required: { + defaultSelected: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + responsive: false, + }, + isDisabled: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + isRequired: { type: 'enum', values: ['boolean'], responsive: false, @@ -48,13 +49,13 @@ export const checkboxPropDefs: Record = { export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; -`; +Accept terms`; -export const checkboxDefaultSnippet = ``; +export const checkboxDefaultSnippet = `Accept terms and conditions`; -export const checkboxVariantsSnippet = ` - - - - -`; +export const checkboxVariantsSnippet = ` + Unchecked + Checked + Disabled + Checked & Disabled +`; diff --git a/docs-ui/src/content/components/avatar.props.ts b/docs-ui/src/content/components/avatar.props.ts deleted file mode 100644 index 8cc53eeff3..0000000000 --- a/docs-ui/src/content/components/avatar.props.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const avatarPropDefs: Record = { - src: { - type: 'string', - }, - name: { - type: 'string', - }, - size: { - type: 'enum', - values: ['small', 'medium', 'large'], - default: 'medium', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const snippetUsage = `import { Avatar } from '@backstage/ui'; - -`; - -export const snippetSizes = ` - - - -`; - -export const snippetFallback = ``; diff --git a/docs-ui/src/content/components/collapsible.mdx b/docs-ui/src/content/components/collapsible.mdx deleted file mode 100644 index 948da323fe..0000000000 --- a/docs-ui/src/content/components/collapsible.mdx +++ /dev/null @@ -1,72 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { CollapsibleSnippet } from '@/snippets/stories-snippets'; -import { - collapsibleRootPropDefs, - collapsibleTriggerPropDefs, - collapsiblePanelPropDefs, - collapsibleUsageSnippet, - collapsibleDefaultSnippet, - collapsibleTriggerSnippet, - collapsibleOpenSnippet, -} from './collapsible.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; - - - -} - code={collapsibleDefaultSnippet} -/> - -## Usage - - - -## API reference - -### Collapsible.Root - -Groups all parts of the collapsible. Renders a `
` element. - - - -### Collapsible.Trigger - -The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend -using the `render` prop to render a custom trigger. - - - - - -### Collapsible.Panel - -A panel with the collapsible contents. Renders a `
` element. - - - -## Examples - -Open the panel by default by setting the `defaultOpen` prop to `true`. - -} - code={collapsibleOpenSnippet} -/> - - - - diff --git a/docs-ui/src/content/components/collapsible.props.ts b/docs-ui/src/content/components/collapsible.props.ts deleted file mode 100644 index 55e80f6689..0000000000 --- a/docs-ui/src/content/components/collapsible.props.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - renderPropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const collapsibleRootPropDefs: Record = { - defaultOpen: { - type: 'boolean', - default: 'false', - }, - open: { - type: 'boolean', - }, - onOpenChange: { - type: 'enum', - values: ['(open) => void'], - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsibleTriggerPropDefs: Record = { - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsiblePanelPropDefs: Record = { - hiddenUntilFound: { - type: 'boolean', - default: 'false', - }, - keepMounted: { - type: 'boolean', - default: 'false', - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsibleUsageSnippet = `import { Collapsible } from '@backstage/ui'; - - - ( - - )} /> - Your content -`; - -export const collapsibleDefaultSnippet = ` - ( - - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`; - -export const collapsibleTriggerSnippet = ` - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`; diff --git a/docs-ui/src/content/components/container.mdx b/docs-ui/src/content/container.mdx similarity index 92% rename from docs-ui/src/content/components/container.mdx rename to docs-ui/src/content/container.mdx index 05615cb1c2..56c2d5d405 100644 --- a/docs-ui/src/content/components/container.mdx +++ b/docs-ui/src/content/container.mdx @@ -11,6 +11,7 @@ import { } from './container.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { ContainerDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/container.props.ts b/docs-ui/src/content/container.props.ts similarity index 100% rename from docs-ui/src/content/components/container.props.ts rename to docs-ui/src/content/container.props.ts diff --git a/docs-ui/src/content/components/dialog.mdx b/docs-ui/src/content/dialog.mdx similarity index 96% rename from docs-ui/src/content/components/dialog.mdx rename to docs-ui/src/content/dialog.mdx index 4aa43c69de..e1e4ec436e 100644 --- a/docs-ui/src/content/components/dialog.mdx +++ b/docs-ui/src/content/dialog.mdx @@ -18,6 +18,7 @@ import { } from './dialog.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { DialogDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/dialog.props.ts b/docs-ui/src/content/dialog.props.ts similarity index 100% rename from docs-ui/src/content/components/dialog.props.ts rename to docs-ui/src/content/dialog.props.ts diff --git a/docs-ui/src/content/components/flex.mdx b/docs-ui/src/content/flex.mdx similarity index 94% rename from docs-ui/src/content/components/flex.mdx rename to docs-ui/src/content/flex.mdx index 89cd2e380a..36284f1f0b 100644 --- a/docs-ui/src/content/components/flex.mdx +++ b/docs-ui/src/content/flex.mdx @@ -14,6 +14,7 @@ import { import { spacingPropDefs } from '@/utils/propDefs'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { FlexDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/flex.props.ts b/docs-ui/src/content/flex.props.ts similarity index 100% rename from docs-ui/src/content/components/flex.props.ts rename to docs-ui/src/content/flex.props.ts diff --git a/docs-ui/src/content/components/grid.mdx b/docs-ui/src/content/grid.mdx similarity index 96% rename from docs-ui/src/content/components/grid.mdx rename to docs-ui/src/content/grid.mdx index 0ffdba6a66..5f179b9e14 100644 --- a/docs-ui/src/content/components/grid.mdx +++ b/docs-ui/src/content/grid.mdx @@ -16,6 +16,7 @@ import { } from './grid.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { GridDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/grid.props.ts b/docs-ui/src/content/grid.props.ts similarity index 100% rename from docs-ui/src/content/components/grid.props.ts rename to docs-ui/src/content/grid.props.ts diff --git a/docs-ui/src/content/components/header-page.mdx b/docs-ui/src/content/header-page.mdx similarity index 95% rename from docs-ui/src/content/components/header-page.mdx rename to docs-ui/src/content/header-page.mdx index d3f93058ee..12ef2c32ee 100644 --- a/docs-ui/src/content/components/header-page.mdx +++ b/docs-ui/src/content/header-page.mdx @@ -14,6 +14,7 @@ import { } from './header-page.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { HeaderPageDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/header-page.props.ts b/docs-ui/src/content/header-page.props.ts similarity index 100% rename from docs-ui/src/content/components/header-page.props.ts rename to docs-ui/src/content/header-page.props.ts diff --git a/docs-ui/src/content/components/header.mdx b/docs-ui/src/content/header.mdx similarity index 94% rename from docs-ui/src/content/components/header.mdx rename to docs-ui/src/content/header.mdx index af4e8010c5..eb505d7a62 100644 --- a/docs-ui/src/content/components/header.mdx +++ b/docs-ui/src/content/header.mdx @@ -13,6 +13,7 @@ import { } from './header.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { HeaderDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/header.props.ts b/docs-ui/src/content/header.props.ts similarity index 100% rename from docs-ui/src/content/components/header.props.ts rename to docs-ui/src/content/header.props.ts diff --git a/docs-ui/src/content/components/link.mdx b/docs-ui/src/content/link.mdx similarity index 95% rename from docs-ui/src/content/components/link.mdx rename to docs-ui/src/content/link.mdx index e0c1be353f..486c06b298 100644 --- a/docs-ui/src/content/components/link.mdx +++ b/docs-ui/src/content/link.mdx @@ -19,6 +19,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { LinkDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/link.props.ts b/docs-ui/src/content/link.props.ts similarity index 100% rename from docs-ui/src/content/components/link.props.ts rename to docs-ui/src/content/link.props.ts diff --git a/docs-ui/src/content/components/menu.mdx b/docs-ui/src/content/menu.mdx similarity index 98% rename from docs-ui/src/content/components/menu.mdx rename to docs-ui/src/content/menu.mdx index 743d884809..9ede0885e2 100644 --- a/docs-ui/src/content/components/menu.mdx +++ b/docs-ui/src/content/menu.mdx @@ -27,6 +27,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { MenuDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/menu.props.ts b/docs-ui/src/content/menu.props.ts similarity index 100% rename from docs-ui/src/content/components/menu.props.ts rename to docs-ui/src/content/menu.props.ts diff --git a/docs-ui/src/content/components/password-field.mdx b/docs-ui/src/content/password-field.mdx similarity index 92% rename from docs-ui/src/content/components/password-field.mdx rename to docs-ui/src/content/password-field.mdx index 14fbc9e34d..8dda2b4f2a 100644 --- a/docs-ui/src/content/components/password-field.mdx +++ b/docs-ui/src/content/password-field.mdx @@ -12,6 +12,7 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; +import { PasswordFieldDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/password-field.props.ts b/docs-ui/src/content/password-field.props.ts similarity index 100% rename from docs-ui/src/content/components/password-field.props.ts rename to docs-ui/src/content/password-field.props.ts diff --git a/docs-ui/src/content/components/radio-group.mdx b/docs-ui/src/content/radio-group.mdx similarity index 95% rename from docs-ui/src/content/components/radio-group.mdx rename to docs-ui/src/content/radio-group.mdx index ab3d28ad81..ba9352c026 100644 --- a/docs-ui/src/content/components/radio-group.mdx +++ b/docs-ui/src/content/radio-group.mdx @@ -16,6 +16,7 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; +import { RadioGroupDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/radio-group.props.ts b/docs-ui/src/content/radio-group.props.ts similarity index 100% rename from docs-ui/src/content/components/radio-group.props.ts rename to docs-ui/src/content/radio-group.props.ts diff --git a/docs-ui/src/content/components/search-field.mdx b/docs-ui/src/content/search-field.mdx similarity index 93% rename from docs-ui/src/content/components/search-field.mdx rename to docs-ui/src/content/search-field.mdx index 2a2d93f262..6b33feea42 100644 --- a/docs-ui/src/content/components/search-field.mdx +++ b/docs-ui/src/content/search-field.mdx @@ -13,6 +13,7 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; +import { SearchFieldDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/search-field.props.ts b/docs-ui/src/content/search-field.props.ts similarity index 100% rename from docs-ui/src/content/components/search-field.props.ts rename to docs-ui/src/content/search-field.props.ts diff --git a/docs-ui/src/content/components/select.mdx b/docs-ui/src/content/select.mdx similarity index 68% rename from docs-ui/src/content/components/select.mdx rename to docs-ui/src/content/select.mdx index 09a852e871..436129d749 100644 --- a/docs-ui/src/content/components/select.mdx +++ b/docs-ui/src/content/select.mdx @@ -11,10 +11,14 @@ import { selectDisabledSnippet, selectResponsiveSnippet, selectIconSnippet, + selectSearchableSnippet, + selectMultipleSnippet, + selectSearchableMultipleSnippet, } from './select.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SelectDefinition } from '../utils/definitions'; +### Searchable + +Here's a view when the select has searchable filtering. + +} + code={selectSearchableSnippet} +/> + +### Multiple Selection + +Here's a view when the select allows multiple selections. + +} + code={selectMultipleSnippet} +/> + +### Searchable with Multiple Selection + +Here's a view when the select combines search and multiple selection. + +} + code={selectSearchableMultipleSnippet} +/> + ### Responsive Here's a view when the select is responsive. - + diff --git a/docs-ui/src/content/components/select.props.ts b/docs-ui/src/content/select.props.ts similarity index 59% rename from docs-ui/src/content/components/select.props.ts rename to docs-ui/src/content/select.props.ts index a93f448bd8..05f0d895c9 100644 --- a/docs-ui/src/content/components/select.props.ts +++ b/docs-ui/src/content/select.props.ts @@ -23,6 +23,12 @@ export const selectPropDefs: Record = { values: ['Array<{ value: string, label: string }>'], required: true, }, + selectionMode: { + type: 'enum', + values: ['single', 'multiple'], + default: 'single', + responsive: false, + }, placeholder: { type: 'string', default: 'Select an item', @@ -34,17 +40,23 @@ export const selectPropDefs: Record = { responsive: false, }, value: { - type: 'string', + type: 'enum', + values: ['string', 'string[]'], responsive: false, + description: + 'Selected value (controlled). String for single selection, array for multiple.', }, defaultValue: { - type: 'string', + type: 'enum', + values: ['string', 'string[]'], responsive: false, + description: + 'Initial value (uncontrolled). String for single selection, array for multiple.', }, size: { type: 'enum', values: ['small', 'medium'], - default: 'medium', + default: 'small', responsive: true, }, isOpen: { @@ -57,7 +69,7 @@ export const selectPropDefs: Record = { }, disabledKeys: { type: 'enum', - values: ['string[]'], + values: ['Iterable'], responsive: false, }, isDisabled: { @@ -72,14 +84,6 @@ export const selectPropDefs: Record = { type: 'boolean', responsive: false, }, - selectedKey: { - type: 'string', - responsive: false, - }, - defaultSelectedKey: { - type: 'string', - responsive: false, - }, onOpenChange: { type: 'enum', values: ['(isOpen: boolean) => void'], @@ -87,7 +91,19 @@ export const selectPropDefs: Record = { }, onSelectionChange: { type: 'enum', - values: ['(key: Key | null) => void'], + values: ['(key: Key | null) => void', '(keys: Selection) => void'], + responsive: false, + description: + 'Handler called when selection changes. Single mode: receives Key | null. Multiple mode: receives Selection.', + }, + searchable: { + type: 'boolean', + default: 'false', + responsive: false, + }, + searchPlaceholder: { + type: 'string', + default: 'Search...', responsive: false, }, ...classNamePropDefs, @@ -151,3 +167,45 @@ export const selectResponsiveSnippet = ``; + +export const selectMultipleSnippet = ``; diff --git a/docs-ui/src/content/components/skeleton.mdx b/docs-ui/src/content/skeleton.mdx similarity index 92% rename from docs-ui/src/content/components/skeleton.mdx rename to docs-ui/src/content/skeleton.mdx index f0d3fbc39a..07cd4b3332 100644 --- a/docs-ui/src/content/components/skeleton.mdx +++ b/docs-ui/src/content/skeleton.mdx @@ -12,6 +12,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SkeletonDefinition } from '../utils/definitions'; - + diff --git a/docs-ui/src/content/components/skeleton.props.ts b/docs-ui/src/content/skeleton.props.ts similarity index 100% rename from docs-ui/src/content/components/skeleton.props.ts rename to docs-ui/src/content/skeleton.props.ts diff --git a/docs-ui/src/content/components/switch.mdx b/docs-ui/src/content/switch.mdx similarity index 91% rename from docs-ui/src/content/components/switch.mdx rename to docs-ui/src/content/switch.mdx index acf588e37d..965cc7832b 100644 --- a/docs-ui/src/content/components/switch.mdx +++ b/docs-ui/src/content/switch.mdx @@ -6,6 +6,7 @@ import { switchPropDefs, snippetUsage } from './switch.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { SwitchDefinition } from '../utils/definitions'; `} /> - + diff --git a/docs-ui/src/content/components/switch.props.ts b/docs-ui/src/content/switch.props.ts similarity index 100% rename from docs-ui/src/content/components/switch.props.ts rename to docs-ui/src/content/switch.props.ts diff --git a/docs-ui/src/content/components/table.mdx b/docs-ui/src/content/table.mdx similarity index 64% rename from docs-ui/src/content/components/table.mdx rename to docs-ui/src/content/table.mdx index 4ea5fd1677..00b7290532 100644 --- a/docs-ui/src/content/components/table.mdx +++ b/docs-ui/src/content/table.mdx @@ -13,7 +13,9 @@ import { tableHybridSnippet, tableCellInteractionsSnippet, tablePaginationSnippet, - tableSelectionSnippet, + tableSelectionActionsSnippet, + tableSelectionModeSnippet, + tableSelectionBehaviorSnippet, tableSortingSnippet, columnPropDefs, rowPropDefs, @@ -22,6 +24,7 @@ import { import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TableDefinition } from '../utils/definitions'; } + code={tableSelectionModeSnippet} +/> + +#### Selection behavior + +Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple. + +} + code={tableSelectionBehaviorSnippet} +/> + +#### With row actions + +With toggle behavior, clicking a row triggers its action when nothing is selected. Once any row is selected, clicking toggles selection instead. + +With replace behavior, clicking selects the row and double-clicking triggers the action. + +} + code={tableSelectionActionsSnippet} +/> ### Row Clicks @@ -128,6 +160,6 @@ This feature is not available yet — let us know if you'd like us to explore it This feature is not available yet — let us know if you'd like us to explore it! - + diff --git a/docs-ui/src/content/components/table.props.ts b/docs-ui/src/content/table.props.ts similarity index 83% rename from docs-ui/src/content/components/table.props.ts rename to docs-ui/src/content/table.props.ts index d46c6b8ea7..21bd5597c6 100644 --- a/docs-ui/src/content/components/table.props.ts +++ b/docs-ui/src/content/table.props.ts @@ -2,7 +2,7 @@ import { classNamePropDefs, stylePropDefs, type PropDef, -} from '../../utils/propDefs'; +} from '@/utils/propDefs'; export const tablePropDefs: Record = { selectionBehavior: { @@ -267,7 +267,7 @@ export const tablePaginationPropDefs: Record = { ...stylePropDefs, }; -export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePagination } from '@backstage/ui'; +export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui'; @@ -275,14 +275,14 @@ export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePaginati - +
`; -export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, Cell, CellProfile, TablePagination, useTable } from '@backstage/ui'; +export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui'; const data = [ { @@ -318,11 +318,60 @@ const { data: paginatedData, paginationProps } = useTable({ src={item.image} href={item.website} /> - - - + + + ))} `; + +export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui'; + +function MyTable() { + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + + return ( + console.log('Opening', key)} + > + + Name + Status + + + + + + + + + + + +
+ ); +}`; + +export const tableSelectionModeSnippet = ` + {/* ... */} +
`; + +export const tableSelectionBehaviorSnippet = ` + {/* ... */} +
`; diff --git a/docs-ui/src/content/components/tabs.mdx b/docs-ui/src/content/tabs.mdx similarity index 95% rename from docs-ui/src/content/components/tabs.mdx rename to docs-ui/src/content/tabs.mdx index 26905d4733..1fc7448eba 100644 --- a/docs-ui/src/content/components/tabs.mdx +++ b/docs-ui/src/content/tabs.mdx @@ -13,6 +13,7 @@ import { } from './tabs.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TabsDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/tabs.props.ts b/docs-ui/src/content/tabs.props.ts similarity index 100% rename from docs-ui/src/content/components/tabs.props.ts rename to docs-ui/src/content/tabs.props.ts diff --git a/docs-ui/src/content/components/tag-group.mdx b/docs-ui/src/content/tag-group.mdx similarity index 95% rename from docs-ui/src/content/components/tag-group.mdx rename to docs-ui/src/content/tag-group.mdx index 8d7fb3c3d0..88e60b31f5 100644 --- a/docs-ui/src/content/components/tag-group.mdx +++ b/docs-ui/src/content/tag-group.mdx @@ -15,6 +15,7 @@ import { } from './tag-group.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TagGroupDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/tag-group.props.ts b/docs-ui/src/content/tag-group.props.ts similarity index 100% rename from docs-ui/src/content/components/tag-group.props.ts rename to docs-ui/src/content/tag-group.props.ts diff --git a/docs-ui/src/content/components/text-field.mdx b/docs-ui/src/content/text-field.mdx similarity index 92% rename from docs-ui/src/content/components/text-field.mdx rename to docs-ui/src/content/text-field.mdx index ad90783ffb..9f14a82975 100644 --- a/docs-ui/src/content/components/text-field.mdx +++ b/docs-ui/src/content/text-field.mdx @@ -10,6 +10,7 @@ import { } from './text-field.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TextFieldDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; @@ -59,6 +60,6 @@ Here's a simple TextField with a description. code={textFieldDescriptionSnippet} /> - + diff --git a/docs-ui/src/content/components/text-field.props.ts b/docs-ui/src/content/text-field.props.ts similarity index 100% rename from docs-ui/src/content/components/text-field.props.ts rename to docs-ui/src/content/text-field.props.ts diff --git a/docs-ui/src/content/components/text.mdx b/docs-ui/src/content/text.mdx similarity index 95% rename from docs-ui/src/content/components/text.mdx rename to docs-ui/src/content/text.mdx index 48b461aa8c..c987b8e5f7 100644 --- a/docs-ui/src/content/components/text.mdx +++ b/docs-ui/src/content/text.mdx @@ -14,6 +14,7 @@ import { } from './text.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TextDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/text.props.ts b/docs-ui/src/content/text.props.ts similarity index 100% rename from docs-ui/src/content/components/text.props.ts rename to docs-ui/src/content/text.props.ts diff --git a/docs-ui/src/content/components/tooltip.mdx b/docs-ui/src/content/tooltip.mdx similarity index 91% rename from docs-ui/src/content/components/tooltip.mdx rename to docs-ui/src/content/tooltip.mdx index dd2add5308..8ebf6539d6 100644 --- a/docs-ui/src/content/components/tooltip.mdx +++ b/docs-ui/src/content/tooltip.mdx @@ -10,6 +10,7 @@ import { } from './tooltip.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { TooltipDefinition } from '../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; - + diff --git a/docs-ui/src/content/components/tooltip.props.ts b/docs-ui/src/content/tooltip.props.ts similarity index 100% rename from docs-ui/src/content/components/tooltip.props.ts rename to docs-ui/src/content/tooltip.props.ts diff --git a/docs-ui/src/content/visually-hidden.mdx b/docs-ui/src/content/visually-hidden.mdx new file mode 100644 index 0000000000..72ffc7a1d4 --- /dev/null +++ b/docs-ui/src/content/visually-hidden.mdx @@ -0,0 +1,52 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { VisuallyHiddenSnippet } from '@/snippets/stories-snippets'; +import { + visuallyHiddenPropDefs, + visuallyHiddenUsageSnippet, + visuallyHiddenDefaultSnippet, + visuallyHiddenExampleUsageSnippet, +} from './visually-hidden.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { VisuallyHiddenDefinition } from '../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={visuallyHiddenDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Example Usage + +Here's an example of providing screen reader context for a list of links in a footer. + +} + code={visuallyHiddenExampleUsageSnippet} + open +/> + + + + diff --git a/docs-ui/src/content/visually-hidden.props.ts b/docs-ui/src/content/visually-hidden.props.ts new file mode 100644 index 0000000000..18a1c503d1 --- /dev/null +++ b/docs-ui/src/content/visually-hidden.props.ts @@ -0,0 +1,47 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const visuallyHiddenPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const visuallyHiddenUsageSnippet = `import { VisuallyHidden } from '@backstage/ui'; + + + This content is visually hidden but accessible to screen readers +`; + +export const visuallyHiddenDefaultSnippet = ` + + This text is followed by a paragraph that is visually hidden but + accessible to screen readers. Try using a screen reader to hear it, or + inspect the DOM to see it's there. + + + This content is visually hidden but accessible to screen readers + +`; + +export const visuallyHiddenExampleUsageSnippet = ` + + Footer links + + + About us + + + Jobs + + + Terms and Conditions + +`; diff --git a/docs-ui/src/css/globals.css b/docs-ui/src/css/globals.css index fa1a61eb4f..dcc348a9ee 100644 --- a/docs-ui/src/css/globals.css +++ b/docs-ui/src/css/globals.css @@ -1,10 +1,9 @@ :root { - --bg: #f4f4f4; - --panel: #fff; - --panel-hover: #fafafa; + --bg: #ffffff; + --bg-hover: #fafafa; --primary: #000; --secondary: #929292; - --action: #fff; + --action: #f3f3f3; --link: #4f5ce0; --font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', @@ -16,12 +15,15 @@ --border: #e5e5e5; --border2: #cdcdcd; --surface-1: #f4f4f4; + --code-bg: #3e444f; + --code-title: #505865; } [data-theme-mode='dark'] { - --bg: #000; - --panel: #181818; - --panel-hover: #202020; + --bg: #000000; + --bg-hover: #202020; + --code-bg: #202020; + --code-title: #292929; --primary: #fff; --secondary: #818181; --action: #202020; diff --git a/docs-ui/src/css/mdx.module.css b/docs-ui/src/css/mdx.module.css index 6a65995003..7cc2422ecd 100644 --- a/docs-ui/src/css/mdx.module.css +++ b/docs-ui/src/css/mdx.module.css @@ -49,3 +49,27 @@ .a:hover { text-decoration: underline; } + +.headingWithAnchor { + position: relative; + scroll-margin-top: 80px; +} + +.anchorLink { + color: inherit; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.anchorHash { + opacity: 0; + transition: opacity 0.2s; + font-size: 0.8em; + color: var(--text-secondary); +} + +.anchorLink:hover .anchorHash { + opacity: 1; +} diff --git a/docs-ui/src/mdx-components.tsx b/docs-ui/src/mdx-components.tsx index 75dbc81df8..bae798bf7c 100644 --- a/docs-ui/src/mdx-components.tsx +++ b/docs-ui/src/mdx-components.tsx @@ -4,10 +4,61 @@ import Image, { ImageProps } from 'next/image'; import { CodeBlock } from '@/components/CodeBlock'; import styles from './css/mdx.module.css'; +// Utility function to generate slug from heading text +function slugify(text: string): string { + return text + .toString() + .toLocaleLowerCase('en-US') + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-') + .replace(/^-+/, '') + .replace(/-+$/, ''); +} + +// Component for heading with anchor link +function HeadingWithAnchor({ + level, + children, + className, +}: { + level: number; + children: ReactNode; + className: string; +}) { + const text = + typeof children === 'string' + ? children + : Array.isArray(children) + ? children.join('') + : ''; + const id = slugify(text); + + const Tag = `h${level}` as keyof JSX.IntrinsicElements; + + return ( + + + {children} + # + + + ); +} + export const formattedMDXComponents: MDXComponents = { h1: ({ children }) =>

{children as ReactNode}

, - h2: ({ children }) =>

{children as ReactNode}

, - h3: ({ children }) =>

{children as ReactNode}

, + h2: ({ children }) => ( + + {children as ReactNode} + + ), + h3: ({ children }) => ( + + {children as ReactNode} + + ), p: ({ children }) =>

{children as ReactNode}

, a: ({ children, href }) => ( @@ -27,7 +78,7 @@ export const formattedMDXComponents: MDXComponents = { `', - prs: ['30013'], - type: 'breaking', - }, - { - components: ['flex', 'container', 'grid', 'box'], - version: '0.5.0', - description: 'Fixes spacing props on layout components', - prs: ['30013'], - type: 'fix', - }, - { - components: ['switch'], - version: '0.5.0', - description: 'New `Switch` component', - prs: ['30251'], - type: 'new', - }, - { - components: ['tabs'], - version: '0.4.0', - description: 'New `Tabs` component', - prs: ['29996'], - type: 'new', - }, - { - components: ['text', 'heading'], - version: '0.4.0', - description: 'Add `truncate` prop to `Text` and `Heading`', - prs: ['29988'], - type: 'new', - }, - { - components: ['menu'], - version: '0.4.0', - description: 'Add combobox option to `Menu`', - prs: ['29986'], - type: 'new', - }, - { - components: ['textfield'], - version: '0.4.0', - description: 'Add icon prop on `TextField`', - prs: ['29820'], - type: 'new', - }, - { - components: ['button', 'button-icon'], - version: '0.4.0', - description: 'Improve icon props on `Button` and `IconButton`', - prs: ['29667'], - }, - { - components: ['text', 'heading'], - version: '0.4.0', - description: - 'Improve the way we treat custom render on `Text` and `Heading`', - prs: ['29989'], - }, - { - components: ['menu'], - version: '0.4.0', - description: 'Improve `Menu` styles', - prs: ['29986'], - }, - { - components: ['textfield'], - version: '0.4.0', - description: 'Improve `TextField` styles', - prs: ['29974'], - }, - { - components: ['textfield'], - version: '0.4.0', - description: 'Improve clear button on `TextField`', - prs: ['29878'], - }, - { - components: ['flex'], - version: '0.4.0', - description: 'Fix spacing props on all layout components', - prs: ['30013'], - }, - { - components: [], - version: '0.4.0', - description: 'Fix - Pin Base UI version', - prs: ['29782'], - }, - { - components: ['select'], - version: '0.4.0', - description: 'Fix - Clicking `Select` label moves focus to trigger', - prs: ['29755'], - }, - { - components: ['datatable'], - version: '0.4.0', - description: 'Fix `DataTable.Pagination` count issue', - prs: ['29688'], - }, - { - components: ['datatable'], - version: '0.3.0', - description: 'Add `DataTable` component', - prs: ['29484', '29603'], - type: 'new', - }, - { - components: ['select'], - version: '0.3.0', - description: 'Add `Select` component', - prs: ['29440'], - type: 'new', - }, - { - components: ['avatar'], - version: '0.3.0', - description: 'Add `Avatar` component', - prs: ['29594'], - type: 'new', - }, - { - components: ['collapsible'], - version: '0.3.0', - description: 'Add `Collapsible` component', - prs: ['29617'], - type: 'new', - }, - { - components: ['textfield'], - version: '0.3.0', - description: 'Add `TextField` component instead of `Field` + `Input`', - prs: ['29364'], - type: 'new', - }, - { - components: ['datatable'], - version: '0.3.0', - description: 'Add `TableCellProfile`', - prs: ['29600'], - type: 'new', - }, - { - components: [], - version: '0.3.0', - description: 'Add breakpoint hooks - `up()` and `down()`', - prs: ['29564'], - type: 'new', - }, - { - components: [], - version: '0.3.0', - description: 'Add gray scale css tokens', - prs: ['29543'], - type: 'new', - }, - { - components: [], - version: '0.3.0', - description: - 'Update CSS styling API using `[data-___]` instead of class names for props', - prs: ['29560'], - }, - { - components: ['checkbox'], - version: '0.3.0', - description: 'Update `Checkbox` dark mode', - prs: ['29544'], - }, - { - components: ['container'], - version: '0.3.0', - description: 'Update `Container` styles', - prs: ['29475'], - }, - { - components: ['menu'], - version: '0.3.0', - description: 'Update `Menu` styles', - prs: ['29351'], - }, - { - components: ['select'], - version: '0.3.0', - description: 'Fix `Select` styles on small sizes + with long option names', - prs: ['29545'], - }, - { - components: ['link'], - version: '0.3.0', - description: 'Fix render prop on `Link`', - prs: ['29247'], - }, - { - components: ['textfield', 'select'], - version: '0.3.0', - description: 'Remove `Field` from `TextField` + `Select`', - prs: ['29482'], - }, - { - components: ['text', 'heading'], - version: '0.3.0', - description: 'Update `textDecoration` to `none` on `Text` / `Heading`', - prs: ['29357'], - }, - { - components: ['text', 'heading'], - version: '0.3.0', - description: 'Update `textDecoration` to `none` on `Text` / `Heading`', - prs: ['29357'], - }, - { - components: [], - version: '0.3.0', - description: 'Docs - Use stories from Storybook for all examples in Nextjs', - prs: ['29306'], - }, - { - components: [], - version: '0.3.0', - description: 'Docs - Add release page (this one 🤗)', - prs: ['29461'], - }, - { - components: [], - version: '0.3.0', - description: 'Docs - Add docs for Menu, Link', - prs: ['29576'], - }, - { - components: [], - version: '0.3.0', - description: 'Fix CSS watch mode', - prs: ['29352'], - }, - { - components: ['menu'], - version: '0.2.0', - description: 'New `Menu` component', - prs: ['29151'], - type: 'new', - }, - { - components: ['button-icon'], - version: '0.2.0', - description: 'New `IconButton` component', - prs: ['29239'], - type: 'new', - }, - { - components: ['scrollarea'], - version: '0.2.0', - description: 'New `ScrollArea` component', - prs: ['29240'], - type: 'new', - }, - { - components: ['button', 'checkbox'], - version: '0.2.0', - description: 'Improve `Button` & `Checkbox` styles', - prs: ['29127', '28789'], - }, - { - components: ['text'], - version: '0.2.0', - description: 'Improve `Text` styles', - prs: ['29200'], - }, - { - components: ['icon'], - version: '0.2.0', - description: 'Renamed `CanonProvider` to `IconProvider`', - prs: ['29002'], - }, - { - components: ['icon'], - version: '0.2.0', - description: 'Added about 40+ new icons', - prs: ['29264'], - }, - { - components: [], - version: '0.2.0', - description: 'Simplified styling into a unique styles.css file', - prs: ['29199'], - }, - { - components: [], - version: '0.2.0', - description: 'Added global styles to Backstage', - prs: ['29137'], - type: 'new', - }, - { - components: [], - version: '0.2.0', - description: 'Update global CSS tokens', - prs: ['28804'], - }, - { - components: ['flex'], - version: '0.2.0', - description: 'Merge Stack + Inline into Flex', - prs: ['28634'], - }, - { - components: ['button'], - version: '0.2.0', - description: 'Improve `Button` types', - prs: ['29205'], - }, - { - components: [], - version: '0.2.0', - description: 'Move font weight and family back to each components', - prs: ['28972'], - }, - { - components: [], - version: '0.2.0', - description: 'Fix custom values in spacing props', - prs: ['28770'], - }, - { - components: [], - version: '0.2.0', - description: 'Multiple updates on the docs site', - prs: ['28760'], - }, +export const changelog = [ + ...changelog_0_9_0, + ...changelog_0_8_2, + ...changelog_0_8_0, + ...changelog_0_7_2, + ...changelog_0_7_1, + ...changelog_0_7_0, + ...changelog_0_6_0, + ...changelog_0_5_0, + ...changelog_0_4_0, + ...changelog_0_3_2, + ...changelog_0_3_0, + ...changelog_0_2_1, + ...changelog_0_2_0, + ...changelog_0_1_0, ]; diff --git a/docs-ui/src/utils/changelogs/v0.1.0.ts b/docs-ui/src/utils/changelogs/v0.1.0.ts new file mode 100644 index 0000000000..cf333e92e9 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.1.0.ts @@ -0,0 +1,116 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_1_0: ChangelogProps[] = [ + { + components: [], + version: '0.1.0', + description: `**BREAKING**: Merged the Stack and Inline component into a single component called Flex.`, + prs: ['28634'], + type: 'breaking', + commitSha: '72c9800', + }, + { + components: [], + version: '0.1.0', + description: `This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.`, + prs: ['28562'], + type: 'breaking', + commitSha: '65f4acc', + }, + { + components: [], + version: '0.1.0', + description: `**BREAKING**: Fixing css structure and making sure that props are applying the correct styles for all responsive values.`, + prs: ['28630'], + type: 'breaking', + commitSha: '1e4ccce', + }, + { + components: [], + version: '0.1.0', + description: `Updated core CSS tokens and fixing the Button component accordingly.`, + prs: ['28789'], + type: 'breaking', + commitSha: '8309bdb', + }, + { + components: [], + version: '0.1.0', + description: `Removed client directive as they are not needed in React 18.`, + prs: ['28626'], + type: 'fix', + commitSha: '989af25', + }, + { + components: [], + version: '0.1.0', + description: `Fix spacing props not being applied for custom values.`, + prs: ['28770'], + type: 'fix', + commitSha: 'f44e5cf', + }, + { + components: [], + version: '0.1.0', + description: `Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits.`, + prs: ['28579'], + type: 'fix', + commitSha: '58ec9e7', + }, + { + components: [], + version: '0.1.0', + description: `Updated core CSS tokens and fixing the Button component accordingly.`, + prs: ['28789'], + type: 'breaking', + commitSha: '8309bdb', + }, + { + components: [], + version: '0.1.0', + description: `Fix spacing props not being applied for custom values.`, + prs: ['28770'], + type: 'fix', + commitSha: 'f44e5cf', + }, + { + components: [], + version: '0.1.0', + description: `**BREAKING**: Merged the Stack and Inline component into a single component called Flex.`, + prs: ['28634'], + type: 'breaking', + commitSha: '72c9800', + }, + { + components: [], + version: '0.1.0', + description: `**BREAKING**: Fixing css structure and making sure that props are applying the correct styles for all responsive values.`, + prs: ['28630'], + type: 'breaking', + commitSha: '1e4ccce', + }, + { + components: [], + version: '0.1.0', + description: `Removed client directive as they are not needed in React 18.`, + prs: ['28626'], + type: 'fix', + commitSha: '989af25', + }, + { + components: [], + version: '0.1.0', + description: `Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits.`, + prs: ['28579'], + type: 'fix', + commitSha: '58ec9e7', + }, + { + components: [], + version: '0.1.0', + description: `This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.`, + prs: ['28562'], + type: 'breaking', + commitSha: '65f4acc', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.2.0.ts b/docs-ui/src/utils/changelogs/v0.2.0.ts new file mode 100644 index 0000000000..b90cec0309 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.2.0.ts @@ -0,0 +1,98 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_2_0: ChangelogProps[] = [ + { + components: ['menu'], + version: '0.2.0', + description: 'New `Menu` component', + prs: ['29151'], + type: 'new', + }, + { + components: ['button-icon'], + version: '0.2.0', + description: 'New `IconButton` component', + prs: ['29239'], + type: 'new', + }, + { + components: ['scrollarea'], + version: '0.2.0', + description: 'New `ScrollArea` component', + prs: ['29240'], + type: 'new', + }, + { + components: ['button', 'checkbox'], + version: '0.2.0', + description: 'Improve `Button` & `Checkbox` styles', + prs: ['29127', '28789'], + }, + { + components: ['text'], + version: '0.2.0', + description: 'Improve `Text` styles', + prs: ['29200'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Renamed `CanonProvider` to `IconProvider`', + prs: ['29002'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Added about 40+ new icons', + prs: ['29264'], + }, + { + components: [], + version: '0.2.0', + description: 'Simplified styling into a unique styles.css file', + prs: ['29199'], + }, + { + components: [], + version: '0.2.0', + description: 'Added global styles to Backstage', + prs: ['29137'], + type: 'new', + }, + { + components: [], + version: '0.2.0', + description: 'Update global CSS tokens', + prs: ['28804'], + }, + { + components: ['flex'], + version: '0.2.0', + description: 'Merge Stack + Inline into Flex', + prs: ['28634'], + }, + { + components: ['button'], + version: '0.2.0', + description: 'Improve `Button` types', + prs: ['29205'], + }, + { + components: [], + version: '0.2.0', + description: 'Move font weight and family back to each components', + prs: ['28972'], + }, + { + components: [], + version: '0.2.0', + description: 'Fix custom values in spacing props', + prs: ['28770'], + }, + { + components: [], + version: '0.2.0', + description: 'Multiple updates on the docs site', + prs: ['28760'], + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.2.1.ts b/docs-ui/src/utils/changelogs/v0.2.1.ts new file mode 100644 index 0000000000..cbf0f9be0f --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.2.1.ts @@ -0,0 +1,44 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_2_1: ChangelogProps[] = [ + { + components: [], + version: '0.2.1', + description: `Internal refactor and fixes to the prop extraction logic for layout components.`, + prs: ['29389'], + type: 'fix', + commitSha: 'f7cb538', + }, + { + components: [], + version: '0.2.1', + description: `Fix types on the Icon component.`, + prs: ['29306'], + type: 'fix', + commitSha: '5e80f0b', + }, + { + components: [], + version: '0.2.1', + description: `Updated styles for the Menu component in Canon.`, + prs: ['29351'], + type: 'fix', + commitSha: '6af7b16', + }, + { + components: [], + version: '0.2.1', + description: `Add global CSS reset for anchor tags.`, + prs: ['29357'], + type: 'new', + commitSha: '513477f', + }, + { + components: [], + version: '0.2.1', + description: `Fix the Icon component when the name is not found to return null instead of an empty SVG.`, + prs: ['29280'], + type: 'fix', + commitSha: '05a5003', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.3.0.ts b/docs-ui/src/utils/changelogs/v0.3.0.ts new file mode 100644 index 0000000000..750ad292cf --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.3.0.ts @@ -0,0 +1,139 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_3_0: ChangelogProps[] = [ + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `DataTable` component', + prs: ['29484', '29603'], + type: 'new', + }, + { + components: ['select'], + version: '0.3.0', + description: 'Add `Select` component', + prs: ['29440'], + type: 'new', + }, + { + components: ['avatar'], + version: '0.3.0', + description: 'Add `Avatar` component', + prs: ['29594'], + type: 'new', + }, + { + components: ['collapsible'], + version: '0.3.0', + description: 'Add `Collapsible` component', + prs: ['29617'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.3.0', + description: 'Add `TextField` component instead of `Field` + `Input`', + prs: ['29364'], + type: 'new', + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `TableCellProfile`', + prs: ['29600'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: 'Add breakpoint hooks - `up()` and `down()`', + prs: ['29564'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: 'Add gray scale css tokens', + prs: ['29543'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: + 'Update CSS styling API using `[data-___]` instead of class names for props', + prs: ['29560'], + }, + { + components: ['checkbox'], + version: '0.3.0', + description: 'Update `Checkbox` dark mode', + prs: ['29544'], + }, + { + components: ['container'], + version: '0.3.0', + description: 'Update `Container` styles', + prs: ['29475'], + }, + { + components: ['menu'], + version: '0.3.0', + description: 'Update `Menu` styles', + prs: ['29351'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Fix `Select` styles on small sizes + with long option names', + prs: ['29545'], + }, + { + components: ['link'], + version: '0.3.0', + description: 'Fix render prop on `Link`', + prs: ['29247'], + }, + { + components: ['textfield', 'select'], + version: '0.3.0', + description: 'Remove `Field` from `TextField` + `Select`', + prs: ['29482'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + prs: ['29357'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + prs: ['29357'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Use stories from Storybook for all examples in Nextjs', + prs: ['29306'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add release page (this one 🤗)', + prs: ['29461'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add docs for Menu, Link', + prs: ['29576'], + }, + { + components: [], + version: '0.3.0', + description: 'Fix CSS watch mode', + prs: ['29352'], + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.3.2.ts b/docs-ui/src/utils/changelogs/v0.3.2.ts new file mode 100644 index 0000000000..26dd225299 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.3.2.ts @@ -0,0 +1,12 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_3_2: ChangelogProps[] = [ + { + components: [], + version: '0.3.2', + description: `Fix Canon missing dependencies`, + prs: ['29642'], + type: 'fix', + commitSha: 'e996368', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.4.0.ts b/docs-ui/src/utils/changelogs/v0.4.0.ts new file mode 100644 index 0000000000..91cd641f4d --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.4.0.ts @@ -0,0 +1,87 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_4_0: ChangelogProps[] = [ + { + components: ['tabs'], + version: '0.4.0', + description: 'New `Tabs` component', + prs: ['29996'], + type: 'new', + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: 'Add `truncate` prop to `Text` and `Heading`', + prs: ['29988'], + type: 'new', + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Add combobox option to `Menu`', + prs: ['29986'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Add icon prop on `TextField`', + prs: ['29820'], + type: 'new', + }, + { + components: ['button', 'button-icon'], + version: '0.4.0', + description: 'Improve icon props on `Button` and `IconButton`', + prs: ['29667'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: + 'Improve the way we treat custom render on `Text` and `Heading`', + prs: ['29989'], + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Improve `Menu` styles', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve `TextField` styles', + prs: ['29974'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve clear button on `TextField`', + prs: ['29878'], + }, + { + components: ['flex'], + version: '0.4.0', + description: 'Fix spacing props on all layout components', + prs: ['30013'], + }, + { + components: [], + version: '0.4.0', + description: 'Fix - Pin Base UI version', + prs: ['29782'], + }, + { + components: ['select'], + version: '0.4.0', + description: 'Fix - Clicking `Select` label moves focus to trigger', + prs: ['29755'], + }, + { + components: ['datatable'], + version: '0.4.0', + description: 'Fix `DataTable.Pagination` count issue', + prs: ['29688'], + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.5.0.ts b/docs-ui/src/utils/changelogs/v0.5.0.ts new file mode 100644 index 0000000000..a48cb96dcc --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.5.0.ts @@ -0,0 +1,86 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_5_0: ChangelogProps[] = [ + { + components: ['textfield'], + version: '0.5.0', + description: '`TextField` now has multiple label sizes.', + prs: ['30249'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: + '`TextField` can now hide label and description while keeping them available for screen readers.', + prs: ['30249'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: + 'We are removing the `render` prop in favour of the `as` prop on `Heading` and `Text`.', + prs: ['30291'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: 'Move `TextField` component to use react Aria under the hood.', + prs: ['30286'], + type: 'breaking', + }, + { + components: ['button', 'button-link'], + version: '0.5.0', + description: + 'Added a render prop to the `Button` component to use it as a link.', + prs: ['30165'], + type: 'new', + }, + { + components: ['heading'], + version: '0.5.0', + description: 'Fix styling for the title4 prop on the Heading component.', + prs: ['30167'], + type: 'fix', + }, + { + components: ['flex'], + version: '0.5.0', + description: + 'Add `min-width: 0;` by default on Flex components to support truncated text.', + prs: ['30168'], + type: 'fix', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.5.0', + description: + '`Button`, `ButtonLink`, `ButtonIcon` now default to size `small` instead of `medium`', + prs: ['30085', '30097'], + type: 'breaking', + }, + { + components: ['grid'], + version: '0.5.0', + description: 'Rename Grid component to ``', + prs: ['30013'], + type: 'breaking', + }, + { + components: ['flex', 'container', 'grid', 'box'], + version: '0.5.0', + description: 'Fixes spacing props on layout components', + prs: ['30013'], + type: 'fix', + }, + { + components: ['switch'], + version: '0.5.0', + description: 'New `Switch` component', + prs: ['30251'], + type: 'new', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.6.0.ts b/docs-ui/src/utils/changelogs/v0.6.0.ts new file mode 100644 index 0000000000..d892f2a78d --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.6.0.ts @@ -0,0 +1,105 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_6_0: ChangelogProps[] = [ + { + components: ['tooltip'], + version: '0.6.0', + description: 'New `Tooltip` component.', + prs: ['30461'], + type: 'new', + }, + { + components: ['card'], + version: '0.6.0', + description: 'New `Card` component.', + prs: ['30467'], + type: 'new', + }, + { + components: ['header'], + version: '0.6.0', + description: 'New `Header` component.', + prs: ['30410', '30459', '30493'], + type: 'new', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: 'Improve `Button`, `ButtonIcon` and `ButtonLink` styling.', + prs: ['30448'], + type: 'fix', + }, + { + components: ['text', 'heading'], + version: '0.6.0', + description: + 'Update return types for `Heading` & `Text` components for React 19.', + prs: ['30440'], + type: 'fix', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: + 'New `tertiary` variant to `Button`, `ButtonIcon` and `ButtonLink`.', + prs: ['30453'], + type: 'new', + }, + { + components: ['skeleton'], + version: '0.6.0', + description: 'New `Skeleton` component.', + prs: ['30466'], + type: 'new', + }, + { + components: ['button-icon'], + version: '0.6.0', + description: 'Rename `IconButton` to `ButtonIcon`.', + prs: ['30297'], + type: 'breaking', + }, + { + components: ['button-link'], + version: '0.6.0', + description: + 'New `ButtonLink`, which replaces the previous render prop pattern on `Button` and `IconButton`.', + prs: ['30297'], + type: 'new', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: 'Remove the `render` prop from all button-related components.', + prs: ['30297'], + type: 'breaking', + }, + { + components: [], + version: '0.6.0', + description: 'We are consolidating all css files into a single styles.css.', + prs: ['30325'], + type: 'fix', + }, + { + components: ['searchfield'], + version: '0.6.0', + description: 'New `SearchField` component.', + prs: ['30357'], + type: 'new', + }, + { + components: ['radio-group'], + version: '0.6.0', + description: 'New `RadioGroup` + `Radio` component.', + prs: ['30327'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.6.0', + description: 'Added placeholder prop back to `TextField` component.', + prs: ['30286'], + type: 'fix', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.7.0.ts b/docs-ui/src/utils/changelogs/v0.7.0.ts new file mode 100644 index 0000000000..be49afc931 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.7.0.ts @@ -0,0 +1,212 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_7_0: ChangelogProps[] = [ + { + components: ['datatable', 'table'], + version: '0.7.0', + description: `We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood.`, + prs: ['30654'], + type: 'breaking', + commitSha: '0615e54', + }, + { + components: ['header-page'], + version: '0.7.0', + description: `Backstage UI - HeaderPage - We are updating the breadcrumb to be more visible and accessible.`, + prs: ['30874'], + type: 'breaking', + commitSha: 'b245c9d', + }, + { + components: ['menu'], + version: '0.7.0', + description: `**Breaking change** We are updating the Menu component to use React Aria under the hood. The structure and all props are changing to follow React Aria's guidance.`, + prs: ['30908'], + type: 'breaking', + commitSha: '800f593', + }, + { + components: ['text', 'heading', 'link'], + version: '0.7.0', + description: `**Breaking** We are upgrading our \`Text\` component to support all font sizes making the \`Heading\` component redundant. The new \`Text\` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the \`as\` prop to include all possible values. The \`Link\` component has also been updated to match the new \`Text\` component.`, + prs: ['30592'], + type: 'breaking', + commitSha: 'b0e47f3', + }, + { + components: ['select'], + version: '0.7.0', + description: `Fixes some styles on the Select component in BUI.`, + prs: ['30642'], + type: 'fix', + commitSha: 'de89a3d', + }, + { + components: ['card'], + version: '0.7.0', + description: `Export CardHeader, CardBody and CardFooter from Card component index`, + prs: ['30882'], + type: 'fix', + commitSha: 'a251b3e', + }, + { + components: ['tag-group'], + version: '0.7.0', + description: `Add new TagGroup component to Backstage UI.`, + prs: ['30919'], + type: 'new', + commitSha: 'f761306', + }, + { + components: ['header', 'header-page'], + version: '0.7.0', + description: `Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage.`, + prs: ['30636'], + type: 'fix', + commitSha: '75fead9', + }, + { + components: ['tooltip'], + version: '0.7.0', + description: `Update styling of Tooltip element`, + prs: ['30591'], + type: 'fix', + commitSha: 'e7ff178', + }, + { + components: ['header-page', 'header'], + version: '0.7.0', + description: `**Breaking change** Move breadcrumb to fit in the \`HeaderPage\` instead of the \`Header\` in Backstage UI.`, + prs: ['30701'], + type: 'fix', + commitSha: '230b410', + }, + { + components: [], + version: '0.7.0', + description: `We are motion away from \`motion\` to use \`gsap\` instead to make Backstage UI backward compatible with React 17.`, + prs: ['30626'], + type: 'fix', + commitSha: '2f9a084', + }, + { + components: ['menu'], + version: '0.7.0', + description: `Updated Menu component in Backstage UI to use useId() from React Aria instead of React to support React 17.`, + prs: ['30675'], + type: 'fix', + commitSha: 'd4e603e', + }, + { + components: ['select'], + version: '0.7.0', + description: `Remove stylesheet import from Select component.`, + prs: ['30800'], + type: 'fix', + commitSha: '8bdc491', + }, + { + components: ['searchfield'], + version: '0.7.0', + description: `Add \`startCollapsed\` prop on the \`SearchField\` component in BUI.`, + prs: ['30729'], + type: 'new', + commitSha: '404b426', + }, + { + components: ['header'], + version: '0.7.0', + description: `Adds onTabSelectionChange to ui header component.`, + prs: ['30588'], + type: 'fix', + commitSha: 'e0e886f', + }, + { + components: ['datatable', 'table'], + version: '0.7.0', + description: `We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood.`, + prs: ['30654'], + type: 'breaking', + commitSha: '0615e54', + }, + { + components: ['header-page', 'header'], + version: '0.7.0', + description: `**Breaking change** Move breadcrumb to fit in the \`HeaderPage\` instead of the \`Header\` in Backstage UI.`, + prs: ['30701'], + type: 'fix', + commitSha: '230b410', + }, + { + components: ['select'], + version: '0.7.0', + description: `Remove stylesheet import from Select component.`, + prs: ['30800'], + type: 'fix', + commitSha: '8bdc491', + }, + { + components: ['searchfield'], + version: '0.7.0', + description: `Add \`startCollapsed\` prop on the \`SearchField\` component in BUI.`, + prs: ['30729'], + type: 'new', + commitSha: '404b426', + }, + { + components: ['menu'], + version: '0.7.0', + description: `Updated Menu component in Backstage UI to use useId() from React Aria instead of React to support React 17.`, + prs: ['30675'], + type: 'fix', + commitSha: 'd4e603e', + }, + { + components: ['select'], + version: '0.7.0', + description: `Fixes some styles on the Select component in BUI.`, + prs: ['30642'], + type: 'fix', + commitSha: 'de89a3d', + }, + { + components: ['header', 'header-page'], + version: '0.7.0', + description: `Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage.`, + prs: ['30636'], + type: 'fix', + commitSha: '75fead9', + }, + { + components: [], + version: '0.7.0', + description: `We are motion away from \`motion\` to use \`gsap\` instead to make Backstage UI backward compatible with React 17.`, + prs: ['30626'], + type: 'fix', + commitSha: '2f9a084', + }, + { + components: ['text', 'heading', 'link'], + version: '0.7.0', + description: `**Breaking** We are upgrading our \`Text\` component to support all font sizes making the \`Heading\` component redundant. The new \`Text\` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the \`as\` prop to include all possible values. The \`Link\` component has also been updated to match the new \`Text\` component.`, + prs: ['30592'], + type: 'breaking', + commitSha: 'b0e47f3', + }, + { + components: ['tooltip'], + version: '0.7.0', + description: `Update styling of Tooltip element`, + prs: ['30591'], + type: 'fix', + commitSha: 'e7ff178', + }, + { + components: ['header'], + version: '0.7.0', + description: `Adds onTabSelectionChange to ui header component.`, + prs: ['30588'], + type: 'fix', + commitSha: 'e0e886f', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.7.1.ts b/docs-ui/src/utils/changelogs/v0.7.1.ts new file mode 100644 index 0000000000..0f9f4cec5c --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.7.1.ts @@ -0,0 +1,44 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_7_1: ChangelogProps[] = [ + { + components: ['flex'], + version: '0.7.1', + description: `Add missing class for flex: baseline`, + prs: ['31013'], + type: 'new', + commitSha: '7307930', + }, + { + components: ['select'], + version: '0.7.1', + description: `Fix Select component to properly attach aria-label and aria-labelledby props to the rendered element for improved accessibility.`, + prs: ['31037'], + type: 'fix', + commitSha: '89da341', + }, + { + components: [], + version: '0.7.1', + description: `Removed the need to mock \`window.matchMedia\` in tests, falling back to default breakpoint values instead.`, + prs: ['31148'], + type: 'fix', + commitSha: '0ffa4c7', + }, + { + components: ['flex'], + version: '0.7.1', + description: `Add missing class for flex: baseline`, + prs: ['31013'], + type: 'new', + commitSha: '7307930', + }, + { + components: ['select'], + version: '0.7.1', + description: `Fix Select component to properly attach aria-label and aria-labelledby props to the rendered element for improved accessibility.`, + prs: ['31037'], + type: 'fix', + commitSha: '89da341', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.7.2.ts b/docs-ui/src/utils/changelogs/v0.7.2.ts new file mode 100644 index 0000000000..4a47d3d0e9 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.7.2.ts @@ -0,0 +1,68 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_7_2: ChangelogProps[] = [ + { + components: ['tabs', 'header'], + version: '0.7.2', + description: `Making href mandatory in tabs that are part of a Header component`, + prs: ['31343'], + type: 'fix', + commitSha: '3c921c5', + }, + { + components: ['button-link'], + version: '0.7.2', + description: `Add react router for internal routing for ButtonLinks`, + prs: ['31276'], + type: 'new', + commitSha: '5c21e45', + }, + { + components: ['tabs'], + version: '0.7.2', + description: `Remove auto selection of tabs for tabs that all have href defined`, + prs: ['31281'], + type: 'fix', + commitSha: '9781815', + }, + { + components: ['menu'], + version: '0.7.2', + description: `Using react router for internal links in the Menu component`, + prs: ['31339'], + type: 'fix', + commitSha: 'f6dff5b', + }, + { + components: ['button', 'tooltip'], + version: '0.7.2', + description: `Enable tooltips on disabled buttons with automatic wrapper`, + prs: ['31230'], + type: 'fix', + commitSha: 'a9b88be', + }, + { + components: [], + version: '0.7.2', + description: `Avoid overriding onChange when spreading props`, + prs: ['31232'], + type: 'fix', + commitSha: '4adbb03', + }, + { + components: ['tabs'], + version: '0.7.2', + description: `remove default selection of tab`, + prs: ['31216'], + type: 'fix', + commitSha: '827340f', + }, + { + components: ['searchfield', 'header'], + version: '0.7.2', + description: `Improved SearchField component flex layout and animations. Fixed SearchField behavior in Header components by switching from width-based transitions to flex-basis transitions for better responsive behavior. Added new Storybook stories to test SearchField integration with Header component.`, + prs: ['31158'], + type: 'fix', + commitSha: '9a47125', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.8.0.ts b/docs-ui/src/utils/changelogs/v0.8.0.ts new file mode 100644 index 0000000000..8a76937150 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.8.0.ts @@ -0,0 +1,172 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_8_0: ChangelogProps[] = [ + { + components: ['password-field', 'textfield'], + version: '0.8.0', + description: `**BREAKING**: Added a new \`PasswordField\` component. As part of this change, the \`password\` and \`search\` types have been removed from \`TextField\`.`, + prs: ['31238'], + type: 'breaking', + commitSha: '9acc1d6', + }, + { + components: [], + version: '0.8.0', + description: `**BREAKING** Restructure Backstage UI component styling to use CSS Modules instead of pure CSS. We don't expect this to be an issue in practice but it is important to call out that all styles are now loaded through CSS modules with generated class names. We are still providing fixed class names for all components to allow anyone to style their Backstage instance.`, + prs: ['31399'], + type: 'breaking', + commitSha: 'b0d11b5', + }, + { + components: ['scrollarea'], + version: '0.8.0', + description: `**BREAKING** The ScrollArea component has been removed from Backstage UI because it did not meet our accessibility standards.`, + prs: ['31409'], + type: 'breaking', + commitSha: '0c53517', + }, + { + components: ['icon'], + version: '0.8.0', + description: `**BREAKING** Remove Icon component in Backstage UI. This component was creating issue for tree-shaking. It is recommended to use icons from @remixicon/react until we found a better alternative in Backstage UI.`, + prs: ['31407'], + type: 'breaking', + commitSha: '7b319c5', + }, + { + components: ['dialog'], + version: '0.8.0', + description: `Adding a new Dialog component to Backstage UI.`, + prs: ['31371'], + type: 'fix', + commitSha: '2591b42', + }, + { + components: ['tabs'], + version: '0.8.0', + description: `remove default selection of tab`, + prs: ['31216'], + type: 'fix', + commitSha: '827340f', + }, + { + components: [], + version: '0.8.0', + description: `Fix margin utility classes in Backstage UI.`, + prs: ['31389'], + type: 'fix', + commitSha: '5dc17cc', + }, + { + components: ['menu'], + version: '0.8.0', + description: `Fix scroll jumping when opening menu in Backstage UI.`, + prs: ['31394'], + type: 'fix', + commitSha: '85faee0', + }, + { + components: ['tabs', 'header'], + version: '0.8.0', + description: `Making href mandatory in tabs that are part of a Header component`, + prs: ['31343'], + type: 'fix', + commitSha: '3c921c5', + }, + { + components: [], + version: '0.8.0', + description: `Update react-aria-components to version 1.13.0`, + prs: ['31367'], + type: 'fix', + commitSha: 'df7d2cf', + }, + { + components: ['table'], + version: '0.8.0', + description: `Fix table sorting icon position in Backstage UI.`, + prs: ['31393'], + type: 'fix', + commitSha: '507ee55', + }, + { + components: ['menu'], + version: '0.8.0', + description: `Add new \`virtualized\`, \`maxWidth\` and \`maxHeight\` props to \`Menu\`, \`MenuListBox\`, \`MenuAutocomplete\` and \`MenuAutocompleteListBox\` to allow for virtalization of long lists inside menus.`, + prs: ['31375'], + type: 'new', + commitSha: '8b7c3c9', + }, + { + components: ['box', 'container', 'flex', 'grid'], + version: '0.8.0', + description: `Added support for data attributes in \`\`, \`\`, \`\`, and \`\` components, ensuring they are correctly applied to the rendered elements.`, + prs: ['31374'], + type: 'new', + commitSha: 'b940062', + }, + { + components: ['scrollarea', 'card'], + version: '0.8.0', + description: `Cleaning up Backstage UI props definitions as well as removing ScrollArea in Card to improve accessibility.`, + prs: ['31404'], + type: 'fix', + commitSha: '206c801', + }, + { + components: ['button-link'], + version: '0.8.0', + description: `Add react router for internal routing for ButtonLinks`, + prs: ['31276'], + type: 'new', + commitSha: '5c21e45', + }, + { + components: [], + version: '0.8.0', + description: `Added a background color default on the body`, + prs: ['31365'], + type: 'new', + commitSha: '865bce8', + }, + { + components: [], + version: '0.8.0', + description: `We are restructuring our CSS to have a better layer structure.`, + prs: ['31362'], + type: 'fix', + commitSha: 'af4d9b4', + }, + { + components: ['searchfield', 'header'], + version: '0.8.0', + description: `Improved SearchField component flex layout and animations. Fixed SearchField behavior in Header components by switching from width-based transitions to flex-basis transitions for better responsive behavior. Added new Storybook stories to test SearchField integration with Header component.`, + prs: ['31158'], + type: 'fix', + commitSha: '9a47125', + }, + { + components: ['tabs'], + version: '0.8.0', + description: `Remove auto selection of tabs for tabs that all have href defined`, + prs: ['31281'], + type: 'fix', + commitSha: '9781815', + }, + { + components: [], + version: '0.8.0', + description: `Avoid overriding onChange when spreading props`, + prs: ['31232'], + type: 'fix', + commitSha: '4adbb03', + }, + { + components: ['menu'], + version: '0.8.0', + description: `Using react router for internal links in the Menu component`, + prs: ['31339'], + type: 'fix', + commitSha: 'f6dff5b', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.8.2.ts b/docs-ui/src/utils/changelogs/v0.8.2.ts new file mode 100644 index 0000000000..1f78c7385b --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.8.2.ts @@ -0,0 +1,44 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_8_2: ChangelogProps[] = [ + { + components: ['text'], + version: '0.8.2', + description: `Fix default text color in Backstage UI`, + prs: ['31429'], + type: 'fix', + commitSha: '26c6a78', + }, + { + components: ['text'], + version: '0.8.2', + description: `Fix the default font size in Backstage UI.`, + prs: ['31435'], + type: 'fix', + commitSha: 'dac851f', + }, + { + components: [], + version: '0.8.2', + description: `Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations.`, + prs: ['31448'], + type: 'fix', + commitSha: '3c0ea67', + }, + { + components: [], + version: '0.8.2', + description: `Fix font smoothing as default in Backstage UI.`, + prs: ['31444'], + type: 'fix', + commitSha: '4eb455c', + }, + { + components: ['text'], + version: '0.8.2', + description: `Fix default font wight and font family in Backstage UI.`, + prs: ['31432'], + type: 'fix', + commitSha: '00bfb83', + }, +]; diff --git a/docs-ui/src/utils/changelogs/v0.9.0.ts b/docs-ui/src/utils/changelogs/v0.9.0.ts new file mode 100644 index 0000000000..f5546b7ba9 --- /dev/null +++ b/docs-ui/src/utils/changelogs/v0.9.0.ts @@ -0,0 +1,354 @@ +import type { ChangelogProps } from '../types'; + +export const changelog_0_9_0: ChangelogProps[] = [ + { + components: ['avatar'], + version: '0.9.0', + prs: ['31566'], + description: `**BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New \`x-small\` size added (1.25rem / 20px) + - \`small\` size unchanged (1.5rem / 24px) + - \`medium\` size unchanged (2rem / 32px, default) + - \`large\` size **changed from 3rem to 2.5rem** (40px) + - New \`x-large\` size added (3rem / 48px) + + Migration: + + \`\`\`diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + \`\`\` + + Added \`purpose\` prop for accessibility control (\`'informative'\` or \`'decoration'\`).`, + type: 'breaking', + commitSha: '539cf26', + }, + { + components: ['checkbox'], + version: '0.9.0', + prs: ['31517'], + description: `**BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - \`checked\` → \`isSelected\` + - \`defaultChecked\` → \`defaultSelected\` + - \`disabled\` → \`isDisabled\` + - \`required\` → \`isRequired\` + - \`label\` prop removed - use \`children\` instead + - CSS: \`bui-CheckboxLabel\` class removed + - Data attribute: \`data-checked\` → \`data-selected\` + - Use without label is no longer supported + + Migration examples: + + Before: + + \`\`\`tsx + + \`\`\` + + After: + + \`\`\`tsx + + Accept terms + + \`\`\` + + Before: + + \`\`\`tsx + + \`\`\` + + After: + + \`\`\`tsx + Option + \`\`\` + + Before: + + \`\`\`tsx + + \`\`\` + + After: + + \`\`\`tsx + + Accessible label + + \`\`\``, + type: 'breaking', + commitSha: '5c614ff', + }, + { + components: ['searchfield', 'textfield'], + version: '0.9.0', + prs: ['31507'], + description: `Fixing styles on SearchField in Backstage UI after migration to CSS modules. \`SearchField\` has now its own set of class names. We previously used class names from \`TextField\` but this approach was creating some confusion so going forward in your theme you'll be able to theme \`TextField\` and \`SearchField\` separately.`, + type: 'breaking', + commitSha: '134151f', + }, + { + components: [], + version: '0.9.0', + prs: ['31744'], + description: `**BREAKING**: Removed central \`componentDefinitions\` object and related type utilities (\`ComponentDefinitionName\`, \`ComponentClassNames\`). + + Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. + + **Migration Guide:** + + If you were using component definitions or class names to build custom components, we recommend migrating to either: + + - Use Backstage UI components directly as building blocks, or + - Duplicate the component CSS in your own stylesheets instead of relying on internal class names`, + type: 'breaking', + commitSha: 'a67670d', + }, + { + components: ['menu', 'switch', 'skeleton', 'header', 'tabs'], + version: '0.9.0', + prs: ['31496'], + description: `**BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes.`, + type: 'breaking', + commitSha: 'b78fc45', + }, + { + components: ['accordion'], + version: '0.9.0', + prs: ['31493'], + description: `**BREAKING**: Removed \`Collapsible\` component. Migrate to \`Accordion\` or use React Aria \`Disclosure\`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + \`\`\`diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + \`\`\` + + CSS classes: \`.bui-CollapsibleRoot\` → \`.bui-Accordion\`, \`.bui-CollapsibleTrigger\` → \`.bui-AccordionTrigger\` (now on heading element), \`.bui-CollapsiblePanel\` → \`.bui-AccordionPanel\` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + \`\`\`tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + \`\`\``, + type: 'breaking', + commitSha: '83c100e', + }, + { + components: ['select'], + version: '0.9.0', + prs: ['31649'], + description: `**BREAKING**: The \`SelectProps\` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts \`searchable\`, \`selectionMode\`, and \`searchPlaceholder\` props to enable filtering and multi-selection modes. + + Migration: If you're using \`SelectProps\` type directly, update from \`SelectProps\` to \`SelectProps<'single' | 'multiple'>\`. Component usage remains backward compatible.`, + type: 'breaking', + commitSha: '816af0f', + }, + { + components: ['header'], + version: '0.9.0', + prs: ['31525'], + description: `Fix broken external links in Backstage UI Header component.`, + type: 'fix', + commitSha: 'd01de00', + }, + { + components: ['select'], + version: '0.9.0', + prs: ['31618'], + description: `Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling.`, + type: 'fix', + commitSha: '35a3614', + }, + { + components: ['password-field', 'searchfield', 'menu'], + version: '0.9.0', + prs: ['31679'], + description: `Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components.`, + type: 'fix', + commitSha: '01476f0', + }, + { + components: [], + version: '0.9.0', + prs: ['31429'], + description: `Fix default text color in Backstage UI`, + type: 'fix', + commitSha: '26c6a78', + }, + { + components: ['text'], + version: '0.9.0', + prs: ['31615'], + description: `Fixed Text component to prevent \`truncate\` prop from being spread to the underlying DOM element.`, + type: 'fix', + commitSha: 'deaa427', + }, + { + components: ['link'], + version: '0.9.0', + prs: ['31524'], + description: `Improved the Link component structure in Backstage UI.`, + type: 'fix', + commitSha: '1059f95', + }, + { + components: ['dialog'], + version: '0.9.0', + prs: ['31673'], + description: `Fixed dialog backdrop appearance in dark mode.`, + type: 'fix', + commitSha: '836b0c7', + }, + { + components: ['table', 'avatar'], + version: '0.9.0', + prs: ['31608'], + description: `Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component.`, + type: 'fix', + commitSha: '6874094', + }, + { + components: ['avatar'], + version: '0.9.0', + prs: ['31623'], + description: `Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions.`, + type: 'fix', + commitSha: '719d772', + }, + { + components: [], + version: '0.9.0', + prs: ['31672'], + description: `Removed \`@base-ui-components/react\` dependency as all components now use React Aria Components.`, + type: 'fix', + commitSha: '6d35a6b', + }, + { + components: [], + version: '0.9.0', + prs: ['31435'], + description: `Fix the default font size in Backstage UI.`, + type: 'fix', + commitSha: 'dac851f', + }, + { + components: [], + version: '0.9.0', + prs: ['31448'], + description: `Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations.`, + type: 'fix', + commitSha: '3c0ea67', + }, + { + components: ['radiogroup'], + version: '0.9.0', + prs: ['31576'], + description: `Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow.`, + type: 'fix', + commitSha: '3b18d80', + }, + { + components: [], + version: '0.9.0', + prs: ['31444'], + description: `Fix font smoothing as default in Backstage UI.`, + type: 'fix', + commitSha: '4eb455c', + }, + { + components: [], + version: '0.9.0', + prs: ['31516'], + description: `Enable tree-shaking of imports other than \`*.css\`.`, + type: 'fix', + commitSha: 'ff9f0c3', + }, + { + components: ['button', 'button-icon'], + version: '0.9.0', + prs: ['31681'], + description: `Added \`loading\` prop to Button and ButtonIcon components for displaying spinner during async operations.`, + type: 'new', + commitSha: '7839e7b', + }, + { + components: ['table'], + version: '0.9.0', + prs: ['31680'], + description: `Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the \`href\` prop.`, + type: 'fix', + commitSha: 'a00fb88', + }, + { + components: [], + version: '0.9.0', + prs: ['31469'], + description: `Set the color-scheme property depending on theme`, + type: 'fix', + commitSha: 'e16ece5', + }, + { + components: ['visually-hidden'], + version: '0.9.0', + prs: ['31484'], + description: `Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers.`, + type: 'new', + commitSha: '1ef3ca4', + }, + { + components: [], + version: '0.9.0', + prs: ['31432'], + description: `Fix default font wight and font family in Backstage UI.`, + type: 'fix', + commitSha: '00bfb83', + }, +]; diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts index bd5b7b1ba1..de369043d1 100644 --- a/docs-ui/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -1,198 +1,127 @@ interface Page { title: string; slug: string; - status?: 'alpha' | 'beta' | 'stable' | 'deprecated' | 'inProgress'; + status?: 'alpha' | 'beta' | 'stable' | 'deprecated' | 'inProgress' | 'new'; } -export const overview: Page[] = [ - { - title: 'Getting Started', - slug: '', - }, - { - title: 'Installation', - slug: 'install', - }, - { - title: 'About', - slug: 'about', - }, - { - title: 'Changelog', - slug: 'changelog', - }, -]; - -export const coreConcepts: Page[] = [ - { - title: 'Layout', - slug: 'layout', - }, - { - title: 'Responsive', - slug: 'responsive', - }, - { - title: 'Theming', - slug: 'theming', - }, -]; - export const layoutComponents: Page[] = [ { title: 'Box', slug: 'box', - status: 'alpha', }, { title: 'Container', slug: 'container', - status: 'alpha', }, { title: 'Grid', slug: 'grid', - status: 'alpha', }, { title: 'Flex', slug: 'flex', - status: 'alpha', }, ]; export const components: Page[] = [ + { + title: 'Accordion', + slug: 'accordion', + }, { title: 'Avatar', slug: 'avatar', - status: 'alpha', }, { title: 'Button', slug: 'button', - status: 'alpha', }, { title: 'ButtonIcon', slug: 'button-icon', - status: 'alpha', }, { title: 'ButtonLink', slug: 'button-link', - status: 'alpha', }, { title: 'Card', slug: 'card', - status: 'alpha', }, { title: 'Checkbox', slug: 'checkbox', - status: 'alpha', - }, - { - title: 'Collapsible', - slug: 'collapsible', - status: 'alpha', }, { title: 'Dialog', slug: 'dialog', - status: 'alpha', }, { title: 'Header', slug: 'header', - status: 'alpha', }, { title: 'HeaderPage', slug: 'header-page', - status: 'alpha', }, { title: 'Link', slug: 'link', - status: 'alpha', }, { title: 'Menu', slug: 'menu', - status: 'alpha', }, { title: 'PasswordField', slug: 'password-field', - status: 'alpha', }, { title: 'RadioGroup', slug: 'radio-group', - status: 'alpha', }, { title: 'SearchField', slug: 'search-field', - status: 'alpha', }, { title: 'Select', slug: 'select', - status: 'alpha', }, { title: 'Skeleton', slug: 'skeleton', - status: 'alpha', }, { title: 'Switch', slug: 'switch', - status: 'alpha', }, { title: 'Table', slug: 'table', - status: 'alpha', }, { title: 'Tabs', slug: 'tabs', - status: 'alpha', }, { title: 'TagGroup', slug: 'tag-group', - status: 'alpha', }, { title: 'Text', slug: 'text', - status: 'alpha', }, { title: 'TextField', slug: 'text-field', - status: 'alpha', }, { title: 'Tooltip', slug: 'tooltip', - status: 'alpha', + }, + { + title: 'VisuallyHidden', + slug: 'visually-hidden', }, ]; - -export type ScreenSize = { - title: string; - slug: string; - width: number; -}; - -export const screenSizes: ScreenSize[] = [ - { title: 'Mobile', slug: 'mobile', width: 390 }, - { title: 'Tablet', slug: 'tablet', width: 768 }, - { title: 'Desktop', slug: 'desktop', width: 1280 }, - { title: 'Wide', slug: 'wide', width: 1600 }, -]; diff --git a/docs-ui/src/utils/definitions.ts b/docs-ui/src/utils/definitions.ts new file mode 100644 index 0000000000..d279d73775 --- /dev/null +++ b/docs-ui/src/utils/definitions.ts @@ -0,0 +1 @@ +export * from '../../../packages/ui/src/definitions'; diff --git a/docs-ui/src/utils/getPageName.ts b/docs-ui/src/utils/getPageName.ts index a3298bc9b7..7a1ccbf2a1 100644 --- a/docs-ui/src/utils/getPageName.ts +++ b/docs-ui/src/utils/getPageName.ts @@ -1,18 +1,6 @@ -import { overview, coreConcepts, components, layoutComponents } from './data'; +import { components, layoutComponents } from './data'; export function getPageName(slug: string): string | null { - // Search in overview pages - const overviewPage = overview.find(p => p.slug === slug); - if (overviewPage) { - return overviewPage.title; - } - - // Search in core concepts array - const coreConcept = coreConcepts.find(c => c.slug === slug); - if (coreConcept) { - return coreConcept.title; - } - // Search in components array const component = components.find(c => c.slug === slug); if (component) { diff --git a/docs-ui/src/utils/playground-context.tsx b/docs-ui/src/utils/playground-context.tsx index 2f8ffbaee4..a1cee6ecba 100644 --- a/docs-ui/src/utils/playground-context.tsx +++ b/docs-ui/src/utils/playground-context.tsx @@ -16,8 +16,8 @@ const PlaygroundContext = createContext<{ setSelectedScreenSizes: (screenSizes: string[]) => void; selectedComponents: string[]; setSelectedComponents: (components: string[]) => void; - selectedTheme: Theme; - setSelectedTheme: (theme: Theme) => void; + selectedTheme: Set; + setSelectedTheme: (keys: Set) => void; selectedThemeName: ThemeName; setSelectedThemeName: (themeName: ThemeName) => void; }>({ @@ -25,7 +25,7 @@ const PlaygroundContext = createContext<{ setSelectedScreenSizes: () => {}, selectedComponents: [], setSelectedComponents: () => {}, - selectedTheme: 'light', + selectedTheme: new Set(['light']), setSelectedTheme: () => {}, selectedThemeName: 'backstage', setSelectedThemeName: () => {}, @@ -40,16 +40,24 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState('light'); + const [selectedTheme, setSelectedTheme] = useState>( + new Set(['light']), + ); const [selectedThemeName, setSelectedThemeName] = useState('backstage'); // Load saved theme from localStorage after hydration useEffect(() => { if (isBrowser) { - const savedTheme = localStorage.getItem('theme-mode') as Theme; - if (savedTheme) { - setSelectedTheme(savedTheme); + const savedThemeString = localStorage.getItem('theme-mode'); + if (savedThemeString) { + // Parse the comma-separated string back into a Set + const themeArray = savedThemeString + .split(',') + .filter(Boolean) as Theme[]; + setSelectedTheme(new Set(themeArray)); + } else { + setSelectedTheme(new Set(['light'])); } } }, [isBrowser]); @@ -68,9 +76,9 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { if (isBrowser) { document.documentElement.setAttribute( 'data-theme-mode', - selectedTheme || 'light', + Array.from(selectedTheme).join(','), ); - localStorage.setItem('theme-mode', selectedTheme || 'light'); + localStorage.setItem('theme-mode', Array.from(selectedTheme).join(',')); } }, [selectedTheme, isBrowser]); diff --git a/docs-ui/src/utils/types.ts b/docs-ui/src/utils/types.ts new file mode 100644 index 0000000000..f2f773ebc2 --- /dev/null +++ b/docs-ui/src/utils/types.ts @@ -0,0 +1,46 @@ +export type Component = + | 'avatar' + | 'box' + | 'button' + | 'button-link' + | 'heading' + | 'text' + | 'button-icon' + | 'icon' + | 'tabs' + | 'menu' + | 'textfield' + | 'datatable' + | 'select' + | 'collapsible' + | 'accordion' + | 'checkbox' + | 'container' + | 'link' + | 'tooltip' + | 'scrollarea' + | 'flex' + | 'switch' + | 'grid' + | 'searchfield' + | 'radio-group' + | 'card' + | 'skeleton' + | 'header' + | 'header-page' + | 'password-field' + | 'table' + | 'visually-hidden' + | 'dialog' + | 'tag-group'; + +export type Version = `${number}.${number}.${number}`; + +export interface ChangelogProps { + components: Component[]; + description: string; + version: Version; + prs: string[]; + type?: 'breaking' | 'new' | 'fix'; + commitSha?: string; +} diff --git a/docs-ui/yarn.lock b/docs-ui/yarn.lock index 538b4947b2..3634aca929 100644 --- a/docs-ui/yarn.lock +++ b/docs-ui/yarn.lock @@ -23,10 +23,51 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.21.0": - version: 7.28.3 - resolution: "@babel/runtime@npm:7.28.3" - checksum: 10/f2415e4dbface7496f6fc561d640b44be203071fb0dfb63fbe338c7d2d2047419cb054ef13d1ebb8fc11e35d2b55aa3045def4b985e8b82aea5d7e58e1133e52 +"@babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10/6c9a70452322ea80b3c9b2a412bcf60771819213a67576c8cec41e88a95bb7bf01fc983754cda35dc19603eef52df22203ccbf7777b9d6316932f9fb77c25163 + languageName: node + linkType: hard + +"@base-ui-components/react@npm:^1.0.0-beta.4": + version: 1.0.0-rc.0 + resolution: "@base-ui-components/react@npm:1.0.0-rc.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@base-ui-components/utils": "npm:0.2.2" + "@floating-ui/react-dom": "npm:^2.1.6" + "@floating-ui/utils": "npm:^0.2.10" + reselect: "npm:^5.1.1" + tabbable: "npm:^6.3.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/ea22dd8206962a1b74fea30f4b20f8b5995bd20155466a5ee7fc7a59cc1632e2ba9615094eac9983dbf8387e50daa8b078024da85b3a8d60cdbc43fb1cfc3b50 + languageName: node + linkType: hard + +"@base-ui-components/utils@npm:0.2.2": + version: 0.2.2 + resolution: "@base-ui-components/utils@npm:0.2.2" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@floating-ui/utils": "npm:^0.2.10" + reselect: "npm:^5.1.1" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/a67803b5637cfce1071963f958306f90784f7934ae0b072b450dbf7f2e3423ea9b40e420672849cfcf2965c94ac5960d2c6a5e04e359f1f76166b929dda932a2 languageName: node linkType: hard @@ -138,14 +179,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.34.4, @codemirror/view@npm:^6.35.0": - version: 6.38.1 - resolution: "@codemirror/view@npm:6.38.1" + version: 6.38.8 + resolution: "@codemirror/view@npm:6.38.8" dependencies: "@codemirror/state": "npm:^6.5.0" crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/e0c5a365608749dd096ba7a930c8393f316bf4c2cacd1465a47a057d0a9f9868ff372a0bb6eb696c926f88411139f79a97a05f8c884bcc380145445cc61e68c8 + checksum: 10/81b1508015a378e4719d0239254173f0c5cd340c2abf96eb488fe5fb474bdb37ec1f010b9890ced774accd7aeb9443e7337cb6a89544b954273e5ddabece7cea languageName: node linkType: hard @@ -401,6 +442,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.7.3": + version: 1.7.3 + resolution: "@floating-ui/core@npm:1.7.3" + dependencies: + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10/a8952ff2673ddf28f12feeb86d90c54949e45bcb1af5758b7672850ac0dadb36d4bd61aa45dad1b6a35ba40d4756d3573afac6610b90502639d7266b91e0864e + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.4": + version: 1.7.4 + resolution: "@floating-ui/dom@npm:1.7.4" + dependencies: + "@floating-ui/core": "npm:^1.7.3" + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10/d3d6a23e7b9804ba56338c7c666590258683af14b6026270d32afc1202f72b5b82cca359004bdc7830bf2463a045da6c7bd4e7d5351218cf270ff94206197971 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.6": + version: 2.1.6 + resolution: "@floating-ui/react-dom@npm:2.1.6" + dependencies: + "@floating-ui/dom": "npm:^1.7.4" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10/fbfd3319b42edb9c156e4e872f500d2edb112bc9cfd1b45892bff16ccf21c2484ddc9c416f7631c2aaaadec1b2f98b205db8a3f89eb78ca870905fcfe3917c35 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.10": + version: 0.2.10 + resolution: "@floating-ui/utils@npm:0.2.10" + checksum: 10/b635ea865a8be2484b608b7157f5abf9ed439f351011a74b7e988439e2898199a9a8b790f52291e05bdcf119088160dc782d98cff45cc98c5a271bc6f51327ae + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.13.0": version: 0.13.0 resolution: "@humanwhocodes/config-array@npm:0.13.0" @@ -650,10 +729,10 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": - version: 1.2.3 - resolution: "@lezer/common@npm:1.2.3" - checksum: 10/dad24e353e4e67d88b203191361ca1dff26c01c2b7b4ae829b668a1d115929334d077217367683e39180c0556510ed2066ea8ddba2b079be7c08a7152208cc87 +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.3.0": + version: 1.3.0 + resolution: "@lezer/common@npm:1.3.0" + checksum: 10/8e195a8e426bc18d4339b3f2a1a7ad39c3b2cfa740c7108657a241985f63bdee5255a5f5cf8d863b878881744288bcb679d16170f0e5bcebb141188b53cfd8c0 languageName: node linkType: hard @@ -669,11 +748,11 @@ __metadata: linkType: hard "@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.2.1": - version: 1.2.1 - resolution: "@lezer/highlight@npm:1.2.1" + version: 1.2.3 + resolution: "@lezer/highlight@npm:1.2.3" dependencies: - "@lezer/common": "npm:^1.0.0" - checksum: 10/fec3082419ee87fb265039b680fbac6796f862d8e3042dcb860e8c5a34291503a74927302b568ff1a626f0d2b5cf8dae02a51cfd200084eb329e5fd1236c3163 + "@lezer/common": "npm:^1.3.0" + checksum: 10/8f787d464f8a036f117a0b23e73ac034d224a57d72501c6559089098a28f127c9e495b90ac7d132acc86199e0b64d4c038f75f9293a37c7c61add52fa1acdb4e languageName: node linkType: hard @@ -705,8 +784,8 @@ __metadata: linkType: hard "@mdx-js/loader@npm:^3.1.0": - version: 3.1.0 - resolution: "@mdx-js/loader@npm:3.1.0" + version: 3.1.1 + resolution: "@mdx-js/loader@npm:3.1.1" dependencies: "@mdx-js/mdx": "npm:^3.0.0" source-map: "npm:^0.7.0" @@ -715,18 +794,19 @@ __metadata: peerDependenciesMeta: webpack: optional: true - checksum: 10/983b8d4949f4d214a17f4ad8597a50de36ace75e93292d2493b5fb6077d69d6abc8230b62f88b818c04f12a01d281d63edb6a0738937f7e259406a73ce998134 + checksum: 10/b78afe06e49b5c0bec9e5b232fbc874ffa721ff6b022bdaa7a29e2b5a44fa9f8282aa2b34f74701ca0b8750b33a775cd3182aaba5202e209f3d66961600c930f languageName: node linkType: hard -"@mdx-js/mdx@npm:^3.0.0, @mdx-js/mdx@npm:^3.1.0": - version: 3.1.0 - resolution: "@mdx-js/mdx@npm:3.1.0" +"@mdx-js/mdx@npm:^3.0.0, @mdx-js/mdx@npm:^3.1.1": + version: 3.1.1 + resolution: "@mdx-js/mdx@npm:3.1.1" dependencies: "@types/estree": "npm:^1.0.0" "@types/estree-jsx": "npm:^1.0.0" "@types/hast": "npm:^3.0.0" "@types/mdx": "npm:^2.0.0" + acorn: "npm:^8.0.0" collapse-white-space: "npm:^2.0.0" devlop: "npm:^1.0.0" estree-util-is-identifier-name: "npm:^3.0.0" @@ -747,19 +827,19 @@ __metadata: unist-util-stringify-position: "npm:^4.0.0" unist-util-visit: "npm:^5.0.0" vfile: "npm:^6.0.0" - checksum: 10/4bd4e1160e2b2bc9ea2b5b93246ce0e34a11ac5fd420ec025e82fb1120a72b80025d9cb205cce6394bb5f0013f209b9ea453cbda4c0ca4f97a2169df60084742 + checksum: 10/b871da2497f6e0f11da1574fe772a50b09b7c177de8df0f821f708bf162febe76c01a572a5c68e860960189209fd66f768c2e747fdb3a1f497c5c32e11890c11 languageName: node linkType: hard -"@mdx-js/react@npm:^3.1.0": - version: 3.1.0 - resolution: "@mdx-js/react@npm:3.1.0" +"@mdx-js/react@npm:^3.1.0, @mdx-js/react@npm:^3.1.1": + version: 3.1.1 + resolution: "@mdx-js/react@npm:3.1.1" dependencies: "@types/mdx": "npm:^2.0.0" peerDependencies: "@types/react": ">=16" react: ">=16" - checksum: 10/cf89d6392c76091622fb647f205e1ab5cbdf5edd4401dde7092138cefc9fbb6d61428aa63557de0bccca3695d5a8854dd4a93b34a27cb8e27369da7eaeaa3e73 + checksum: 10/52a740e2f37761694fa94d4704b7825084b4055616a95c8b8f4c1676190d399ddc5cdbb399ffc45b550beecd30497a7224c2e5b05bf43ecb668c7473641037d1 languageName: node linkType: hard @@ -774,25 +854,25 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:15.4.7": - version: 15.4.7 - resolution: "@next/env@npm:15.4.7" - checksum: 10/8341fa6f1b5aebb6f99d8abad2f3ee2685281aaa51785f8ceff3c9a7f03186645f372003ee833715c0ce05e4e7d960defa4252018977f19564001b872b24714e +"@next/env@npm:15.5.7": + version: 15.5.7 + resolution: "@next/env@npm:15.5.7" + checksum: 10/11f971691018bd62a5bf253fc843fb2a6cf1431468f5c3a9d4d41753a6ff3e8bf7f539f46aba3f58f8bac59e681bf05fb5d771ac08d7dbd966a601257e1368bf languageName: node linkType: hard -"@next/eslint-plugin-next@npm:15.3.4": - version: 15.3.4 - resolution: "@next/eslint-plugin-next@npm:15.3.4" +"@next/eslint-plugin-next@npm:15.5.7": + version: 15.5.7 + resolution: "@next/eslint-plugin-next@npm:15.5.7" dependencies: fast-glob: "npm:3.3.1" - checksum: 10/8a473bd32a06f62c16f60f9c40b7b8149cf91170ce4c1546cda57e9c85ac8481c7ad3aa4a77e8c3c7000e80d3de809d2db7df6cb79e13fd258fff382729abf60 + checksum: 10/5b59860aeccc0d07a979c40e4405b410a9d7aa86a235d5dc0edbac8d2da35586f6f2fa3407cd3d80fd7e7d4f197c70291e9c6e71a64942d09b8fe82470e90ee0 languageName: node linkType: hard -"@next/mdx@npm:15.3.4": - version: 15.3.4 - resolution: "@next/mdx@npm:15.3.4" +"@next/mdx@npm:15.5.7": + version: 15.5.7 + resolution: "@next/mdx@npm:15.5.7" dependencies: source-map: "npm:^0.7.0" peerDependencies: @@ -803,62 +883,62 @@ __metadata: optional: true "@mdx-js/react": optional: true - checksum: 10/07904beda049317e43857f9dfe659adb6e57c4417475de1d21a7605f8ace5d9aa992a06465d1a94fe4b62331901765b15659555e8aadedbd410f86df1bebd1bd + checksum: 10/caf2ad1e3a8b02381ad33fe32f59e2ef4c15c96a04ba2a7d713991c142214d3b69d25b879ffc12da1c333ffd34d30aec44061d740f708c8e12529b183a159146 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-darwin-arm64@npm:15.4.7" +"@next/swc-darwin-arm64@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-darwin-arm64@npm:15.5.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-darwin-x64@npm:15.4.7" +"@next/swc-darwin-x64@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-darwin-x64@npm:15.5.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-linux-arm64-gnu@npm:15.4.7" +"@next/swc-linux-arm64-gnu@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-linux-arm64-gnu@npm:15.5.7" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-linux-arm64-musl@npm:15.4.7" +"@next/swc-linux-arm64-musl@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-linux-arm64-musl@npm:15.5.7" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-linux-x64-gnu@npm:15.4.7" +"@next/swc-linux-x64-gnu@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-linux-x64-gnu@npm:15.5.7" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-linux-x64-musl@npm:15.4.7" +"@next/swc-linux-x64-musl@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-linux-x64-musl@npm:15.5.7" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-win32-arm64-msvc@npm:15.4.7" +"@next/swc-win32-arm64-msvc@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-win32-arm64-msvc@npm:15.5.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:15.4.7": - version: 15.4.7 - resolution: "@next/swc-win32-x64-msvc@npm:15.4.7" +"@next/swc-win32-x64-msvc@npm:15.5.7": + version: 15.5.7 + resolution: "@next/swc-win32-x64-msvc@npm:15.5.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -919,6 +999,130 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/auth-token@npm:6.0.0" + checksum: 10/a30f5c4c984964b57193de5b6f67169f74e4779fedbe716157dd3558dd9de3ca5c105cae521b7bd8ce1ae180773a2ef01afe2306ad5a329f4fd291eba2b7c7d1 + languageName: node + linkType: hard + +"@octokit/core@npm:^7.0.6": + version: 7.0.6 + resolution: "@octokit/core@npm:7.0.6" + dependencies: + "@octokit/auth-token": "npm:^6.0.0" + "@octokit/graphql": "npm:^9.0.3" + "@octokit/request": "npm:^10.0.6" + "@octokit/request-error": "npm:^7.0.2" + "@octokit/types": "npm:^16.0.0" + before-after-hook: "npm:^4.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/852d41fc3150d2a891156427dd0575c77889f1c7a109894ee541594e3fd47c0d4e0a93fee22966c507dfd6158b522e42846c2ac46b9d896078194c95fa81f4ae + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^11.0.2": + version: 11.0.2 + resolution: "@octokit/endpoint@npm:11.0.2" + dependencies: + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10/0d088747baf94eafbba69da23ba840b40cd3f5d0bfbc51c692ff9d9d78de6d81f06366e6e30df8c1783355be826c27d38ab9ab0708396af8f430b06cfa29db35 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^9.0.3": + version: 9.0.3 + resolution: "@octokit/graphql@npm:9.0.3" + dependencies: + "@octokit/request": "npm:^10.0.6" + "@octokit/types": "npm:^16.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10/7b16f281f8571dce55280b3986fbb8d15465a7236164a5f6497ded7597ff9ee95d5796924555b979903fe8c6706fe6be1b3e140d807297f85ac8edeadc28f9fe + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^27.0.0": + version: 27.0.0 + resolution: "@octokit/openapi-types@npm:27.0.0" + checksum: 10/5cd2cdf4e41fdf522e15e3d53f3ece8380d98dda9173a6fc905828fb2c33e8733d5f5d2a757ae3a572525f4749748e66cb40e7939372132988d8eb4ba978d54f + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:^14.0.0": + version: 14.0.0 + resolution: "@octokit/plugin-paginate-rest@npm:14.0.0" + dependencies: + "@octokit/types": "npm:^16.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/57ddd857528dad9c02431bc6254c2374c06057872cf9656a4a88b162ebe1c2bc9f34fbec360f2ccff72c940f29b120758ce14e8135bd027223d381eb1b8b6579 + languageName: node + linkType: hard + +"@octokit/plugin-request-log@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/plugin-request-log@npm:6.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/8a79973b1429bfead9113c4117f418aaef5ff368795daded3415ba14623d97d5fc08d1e822dbd566ecc9f041119e1a48a11853a9c48d9eb1caa62baa79c17f83 + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:^17.0.0": + version: 17.0.0 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:17.0.0" + dependencies: + "@octokit/types": "npm:^16.0.0" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10/e9d9ad4d9755cc7fb82fdcbfa870ddea8a432180f0f76c8469095557fd1e26f8caea8cae58401209be17c4f3d8cc48c0e16a3643e37e48f4d23c39e058bf2c55 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^7.0.2": + version: 7.0.2 + resolution: "@octokit/request-error@npm:7.0.2" + dependencies: + "@octokit/types": "npm:^16.0.0" + checksum: 10/8edfaca9f5271115b090f470ebfe1006841ee152dd52cb39786e026236e2c13545aa3cf3187b7b1de717167696a42f91d8fa2f870bf9be0832fb2b11d11a741d + languageName: node + linkType: hard + +"@octokit/request@npm:^10.0.6": + version: 10.0.6 + resolution: "@octokit/request@npm:10.0.6" + dependencies: + "@octokit/endpoint": "npm:^11.0.2" + "@octokit/request-error": "npm:^7.0.2" + "@octokit/types": "npm:^16.0.0" + fast-content-type-parse: "npm:^3.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10/b8c5cd43d3225c8c3b803e889ff251808ae180c953ca92642522843123dafcd84d919b20ca2969b6e4080ee3c2197a99afda15b5649ee51d0715f8249a5c9ca4 + languageName: node + linkType: hard + +"@octokit/rest@npm:^22.0.1": + version: 22.0.1 + resolution: "@octokit/rest@npm:22.0.1" + dependencies: + "@octokit/core": "npm:^7.0.6" + "@octokit/plugin-paginate-rest": "npm:^14.0.0" + "@octokit/plugin-request-log": "npm:^6.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:^17.0.0" + checksum: 10/ec2e94cfa8766716faeb3ca18527d9af746482d35aaa6e4265a30cb669ae3f31f4ebb6235edebe5ae62bc2cec2b8e88902584f698df2e7cabac3a15fd27da665 + languageName: node + linkType: hard + +"@octokit/types@npm:^16.0.0": + version: 16.0.0 + resolution: "@octokit/types@npm:16.0.0" + dependencies: + "@octokit/openapi-types": "npm:^27.0.0" + checksum: 10/03d5cfc29556a9b53eae8beb1bf15c0b704dc722db2c51b53f093f3c3ee6c1d8e20b682be8117a3a17034b458be7746d1b22aaefb959ceb5152ad7589b39e2c9 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -927,11 +1131,11 @@ __metadata: linkType: hard "@remixicon/react@npm:^4.6.0": - version: 4.6.0 - resolution: "@remixicon/react@npm:4.6.0" + version: 4.7.0 + resolution: "@remixicon/react@npm:4.7.0" peerDependencies: react: ">=18.2.0" - checksum: 10/d92c8131734062fca8d82d55dd8f3f4e28cb72c5eefda528c8aba5f3298f0c847342eecdbd7787d6da5af21b699d735f6cf27f151f0c982c1c9f06a78b235c94 + checksum: 10/9ecee093dd4aec3744bcc7562eddb987bf9c7c059787dfecfbcc25a64c2f4833dc815dff715aa2a7f75011d65f877b3751a401d19cf2d725b5bcc685587d37f4 languageName: node linkType: hard @@ -949,74 +1153,74 @@ __metadata: languageName: node linkType: hard -"@shikijs/core@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/core@npm:3.13.0" +"@shikijs/core@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/core@npm:3.19.0" dependencies: - "@shikijs/types": "npm:3.13.0" + "@shikijs/types": "npm:3.19.0" "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" hast-util-to-html: "npm:^9.0.5" - checksum: 10/c9b301a170cd76fa2ce446adee26b539b19204107a2e60207f3f5e7d96773df322367f6035e2d689d43f97a3e9f741793e976c366015d8e3a9709dcac1399af2 + checksum: 10/78fc5f849636459e267cd77f3e60b492c8260a8be9449d3b1b3a463eab22153354ca6c961e021f936cb8251695298d85913b2cb3b31aafa343fdddadebbfed1c languageName: node linkType: hard -"@shikijs/engine-javascript@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/engine-javascript@npm:3.13.0" +"@shikijs/engine-javascript@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/engine-javascript@npm:3.19.0" dependencies: - "@shikijs/types": "npm:3.13.0" + "@shikijs/types": "npm:3.19.0" "@shikijs/vscode-textmate": "npm:^10.0.2" - oniguruma-to-es: "npm:^4.3.3" - checksum: 10/acd4e78fdc732738c6a3a9f254011b7d8995a2149f2f72f7396d5e4514f3b0c3fdc767d52bf8852d65066e613dd8d1157bc4900442ab42684db7ef7396a44871 + oniguruma-to-es: "npm:^4.3.4" + checksum: 10/7b493cfce620976efd1d7cb3df946370897cac0c16fb71b089be9e197fde43ee60a3d1317db45820c61bcae47ae8e4ac4df91555454e733cd239dc064ef6cc03 languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/engine-oniguruma@npm:3.13.0" +"@shikijs/engine-oniguruma@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/engine-oniguruma@npm:3.19.0" dependencies: - "@shikijs/types": "npm:3.13.0" + "@shikijs/types": "npm:3.19.0" "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/bc7f1c69640ccece9a3ac3f3adc9cebde9c24bcd722747cc464c338a32725af2b9087a42646cd0f56fbea98d99414918c558f2ca0beadc13db0a740183e7707f + checksum: 10/6bf6e35aa61f62b1a532071d11e1937a67fb68469c1886ab3c06c6d384b8feee589973ee101cbebefad9ff9405040ee640ced429e83b9dddd906f07d994a1939 languageName: node linkType: hard -"@shikijs/langs@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/langs@npm:3.13.0" +"@shikijs/langs@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/langs@npm:3.19.0" dependencies: - "@shikijs/types": "npm:3.13.0" - checksum: 10/2a0478246ce61745d9012cf5051c9790ef5afad6c89fe5716be4a81d04d7dfd9ffc90af0bad0be0061111bfc001a3bc0c55ffd4fc0b82bc1c09a4c33d7b593ca + "@shikijs/types": "npm:3.19.0" + checksum: 10/679d61b89132c858bd1e96fc7a46fbae56dc8e9be176586cabb57cd9504bb7ea45bbd22879f63d822957060fe322302ad9824b9a6f18415a86b54e22347c0c2d languageName: node linkType: hard -"@shikijs/themes@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/themes@npm:3.13.0" +"@shikijs/themes@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/themes@npm:3.19.0" dependencies: - "@shikijs/types": "npm:3.13.0" - checksum: 10/4ab0ecf1ddb35e387cef22e267cdf3ed7548a84f24ad58885d19401f75cc01a5a10152ee0196222e01cead8202f20012615aa0076f798599f7dfc2bb7beaa617 + "@shikijs/types": "npm:3.19.0" + checksum: 10/7eb53912fe8b877e6fc498c937188ae55b960c681f65cd4201f2c3a919b0f5951f9ce090819ded5dc4eb0aabe7a811ba705a19b5c003e0dbfebad08d1ba535d5 languageName: node linkType: hard "@shikijs/transformers@npm:^3.13.0": - version: 3.13.0 - resolution: "@shikijs/transformers@npm:3.13.0" + version: 3.19.0 + resolution: "@shikijs/transformers@npm:3.19.0" dependencies: - "@shikijs/core": "npm:3.13.0" - "@shikijs/types": "npm:3.13.0" - checksum: 10/30f3e66334b60320dc976b9f645f823ba13b312aead4c891ae6db23d076f1570dad488443f603fb5eecd5937973ba7c2907da6678ccae2363a66e0c5b173bb02 + "@shikijs/core": "npm:3.19.0" + "@shikijs/types": "npm:3.19.0" + checksum: 10/813c2e4053007a8516ebae8c06f4fa2e7b42c6570ea4cae88ecd8bb749695be023ca3b271f8152559dbb74ed36404a77f76c14f63632dc44f225d963a446b1d1 languageName: node linkType: hard -"@shikijs/types@npm:3.13.0": - version: 3.13.0 - resolution: "@shikijs/types@npm:3.13.0" +"@shikijs/types@npm:3.19.0": + version: 3.19.0 + resolution: "@shikijs/types@npm:3.19.0" dependencies: "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" - checksum: 10/a57cc95847edd84134166de1f11a044e356581834b2dfd75dcd67f2221aef6f20f505839c090a76b7c8e0a79d7d5cee829de50d261f363a633ae9afca29546c4 + checksum: 10/3f0e79f2ecbb0754ad08626e14313701a15dd1dbda1a1295d1e35a172b4555339a135118fabcc3ebdbf5017ceb64dbe3e5096268c3034c0c91ba3b0848e4b162 languageName: node linkType: hard @@ -1390,9 +1594,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.25.1": - version: 4.25.1 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.1" +"@uiw/codemirror-extensions-basic-setup@npm:4.25.3": + version: 4.25.3 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.3" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -1409,13 +1613,13 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/61d128a7896e086c3cc0558e0b5f85715f5387ee983cc9f1e341db4b383ade1973f389a63cdf9312a5421a0f5a6044755a573d6dd7bb805dc256d9f9ce4f0d15 + checksum: 10/6ece4ee0fd6433a81bb6b2e18623db3df097d31d13c53184fbf4355cf3aaac5201d73dfa984f58390f0f6f7cfb631bd2ee20f3613b99b7259e2e1352e954c179 languageName: node linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.25.1 - resolution: "@uiw/codemirror-themes@npm:4.25.1" + version: 4.25.3 + resolution: "@uiw/codemirror-themes@npm:4.25.3" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1424,19 +1628,19 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/337316f7c15a57bca4824498769448e38861197ab3d73b3bc58b2193028751c525a175af06f1f3984fd25b001f990e1d30ce442ddc756642698d9f8adff7f95a + checksum: 10/f808ac3c4763f623ed6ea6215cc1421bb87234e0a05e7b6d9e917ab304fc6a4977c238996ca538e22093af8136c02bb27512aeeac653cb8fe82b4917ba1668a3 languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.23.7": - version: 4.25.1 - resolution: "@uiw/react-codemirror@npm:4.25.1" + version: 4.25.3 + resolution: "@uiw/react-codemirror@npm:4.25.3" dependencies: "@babel/runtime": "npm:^7.18.6" "@codemirror/commands": "npm:^6.1.0" "@codemirror/state": "npm:^6.1.1" "@codemirror/theme-one-dark": "npm:^6.0.0" - "@uiw/codemirror-extensions-basic-setup": "npm:4.25.1" + "@uiw/codemirror-extensions-basic-setup": "npm:4.25.3" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -1446,7 +1650,7 @@ __metadata: codemirror: ">=6.0.0" react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/32311a3b2037bf57533bd4d7c50117385c6e3cfce8dcd45f2c2ff8d9170f8ae53bc868b4ac91a58f5502ed065fce11bfffb8af5e829e8a5609290053dcdb0b2e + checksum: 10/a3ece671a48cce623da6c9d890f07bdbb1597b2fb2a6f8f6ef65f2b14b5d5b4fa90791476539b4fba3ac13d226fe00d283a5165d3632f34f5db3b49139a8887d languageName: node linkType: hard @@ -1866,6 +2070,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^4.0.0": + version: 4.0.0 + resolution: "before-after-hook@npm:4.0.0" + checksum: 10/9fd52bc0c3cca0fb115e04dacbeeaacff38fa23e1af725d62392254c31ef433b15da60efcba61552e44d64e26f25ea259f72dba005115924389e88d2fd56e19f + languageName: node + linkType: hard + "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -1990,7 +2201,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -2061,14 +2272,10 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10/cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 languageName: node linkType: hard @@ -2144,26 +2351,6 @@ __metadata: languageName: node linkType: hard -"concurrently@npm:^8.2.2": - version: 8.2.2 - resolution: "concurrently@npm:8.2.2" - dependencies: - chalk: "npm:^4.1.2" - date-fns: "npm:^2.30.0" - lodash: "npm:^4.17.21" - rxjs: "npm:^7.8.1" - shell-quote: "npm:^1.8.1" - spawn-command: "npm:0.0.2" - supports-color: "npm:^8.1.1" - tree-kill: "npm:^1.2.2" - yargs: "npm:^17.7.2" - bin: - conc: dist/bin/concurrently.js - concurrently: dist/bin/concurrently.js - checksum: 10/dcb1aa69d9c611a7bda9d4fc0fe1e388f971d1744acec7e0d52dffa2ef55743f1266ec9292f414c5789b9f61734b3fce772bd005d4de9564a949fb121b97bae1 - languageName: node - linkType: hard - "crelt@npm:^1.0.5, crelt@npm:^1.0.6": version: 1.0.6 resolution: "crelt@npm:1.0.6" @@ -2229,15 +2416,6 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.30.0": - version: 2.30.0 - resolution: "date-fns@npm:2.30.0" - dependencies: - "@babel/runtime": "npm:^7.21.0" - checksum: 10/70b3e8ea7aaaaeaa2cd80bd889622a4bcb5d8028b4de9162cbcda359db06e16ff6e9309e54eead5341e71031818497f19aaf9839c87d1aba1e27bb4796e758a9 - languageName: node - linkType: hard - "debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0": version: 4.4.1 resolution: "debug@npm:4.4.1" @@ -2331,12 +2509,14 @@ __metadata: version: 0.0.0-use.local resolution: "docs-ui@workspace:." dependencies: + "@base-ui-components/react": "npm:^1.0.0-beta.4" "@codemirror/lang-sass": "npm:^6.0.2" "@codemirror/view": "npm:^6.34.4" "@lezer/highlight": "npm:^1.2.1" "@mdx-js/loader": "npm:^3.1.0" "@mdx-js/react": "npm:^3.1.0" - "@next/mdx": "npm:15.3.4" + "@next/mdx": "npm:15.5.7" + "@octokit/rest": "npm:^22.0.1" "@remixicon/react": "npm:^4.6.0" "@shikijs/transformers": "npm:^3.13.0" "@storybook/react": "npm:^8.6.12" @@ -2347,13 +2527,13 @@ __metadata: "@uiw/codemirror-themes": "npm:^4.23.7" "@uiw/react-codemirror": "npm:^4.23.7" chokidar: "npm:^3.6.0" - concurrently: "npm:^8.2.2" + clsx: "npm:^2.1.1" eslint: "npm:^8" - eslint-config-next: "npm:15.3.4" + eslint-config-next: "npm:15.5.7" html-react-parser: "npm:^5.2.5" lightningcss: "npm:^1.28.2" motion: "npm:^12.4.1" - next: "npm:15.4.7" + next: "npm:15.5.7" next-mdx-remote-client: "npm:^2.1.2" prop-types: "npm:^15.8.1" react: "npm:19.1.1" @@ -2361,6 +2541,7 @@ __metadata: shiki: "npm:^3.13.0" storybook: "npm:^8.6.12" typescript: "npm:^5" + unified: "npm:^11.0.4" languageName: unknown linkType: soft @@ -2754,13 +2935,6 @@ __metadata: languageName: node linkType: hard -"escalade@npm:^3.1.1": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -2768,11 +2942,11 @@ __metadata: languageName: node linkType: hard -"eslint-config-next@npm:15.3.4": - version: 15.3.4 - resolution: "eslint-config-next@npm:15.3.4" +"eslint-config-next@npm:15.5.7": + version: 15.5.7 + resolution: "eslint-config-next@npm:15.5.7" dependencies: - "@next/eslint-plugin-next": "npm:15.3.4" + "@next/eslint-plugin-next": "npm:15.5.7" "@rushstack/eslint-patch": "npm:^1.10.3" "@typescript-eslint/eslint-plugin": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" "@typescript-eslint/parser": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2788,7 +2962,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/6c21254d3383b9158ff5f3b2881cc702bee3d2635b4326757965945691f6e65e25fdfef4f2964382fb4b2f52d9f03b929cb71d267709727df7365e7da80c8c3a + checksum: 10/fd1813c7ef4e33ec297b86dd0de75879e10514b12cb04a103e7a5b8c5f27d2ea0a91dd15e6d530006c9fe3e7e18f815e900bd6ba117c3b8fbe311c38c340d899 languageName: node linkType: hard @@ -3137,6 +3311,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-content-type-parse@npm:3.0.0" + checksum: 10/8616a8aa6c9b4f8f4f3c90eaa4e7bfc2240cfa6f41f0eef5b5aa2b2c8b38bd9ad435f1488b6d817ffd725c54651e2777b882ae9dd59366e71e7896f1ec11d473 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -3270,11 +3451,11 @@ __metadata: languageName: node linkType: hard -"framer-motion@npm:^12.23.12": - version: 12.23.12 - resolution: "framer-motion@npm:12.23.12" +"framer-motion@npm:^12.23.25": + version: 12.23.25 + resolution: "framer-motion@npm:12.23.25" dependencies: - motion-dom: "npm:^12.23.12" + motion-dom: "npm:^12.23.23" motion-utils: "npm:^12.23.6" tslib: "npm:^2.4.0" peerDependencies: @@ -3288,7 +3469,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/c15af3dc22476bf10d7509b9a336d113ed0fbeba57fca81ce5a6bf07f0d659ebc3588c3a839475cbdfc9601b64e2fc7551911e1c806dd2b896603c8a2c0c3870 + checksum: 10/0b55dc0a5319cd923cdddcb42726352f0d74a8160f5222a3f090af53bbb2ed0d93c8482da41c9ffa875a3f7f666586e43be7594dca3612e70a1dea6bfbefebb4 languageName: node linkType: hard @@ -3355,13 +3536,6 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 - languageName: node - linkType: hard - "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -3630,31 +3804,31 @@ __metadata: languageName: node linkType: hard -"html-dom-parser@npm:5.1.1": - version: 5.1.1 - resolution: "html-dom-parser@npm:5.1.1" +"html-dom-parser@npm:5.1.2": + version: 5.1.2 + resolution: "html-dom-parser@npm:5.1.2" dependencies: domhandler: "npm:5.0.3" htmlparser2: "npm:10.0.0" - checksum: 10/85bbaa8699d40d54fd64f5472088199178a6c3dd6a907358ac476d9c511cb8ab10bff1469a785411e8b831a73ac08348392e56076eab6d100a3ec19dccc722ad + checksum: 10/d54fcdafbc0b2881c441a396965ad285072d2e4df62bebe7074d7aaa2a1fffc95e9286bcb81d6f241c4cdb10a84435fa623738985b635574acb3c84e994e8bb6 languageName: node linkType: hard "html-react-parser@npm:^5.2.5": - version: 5.2.6 - resolution: "html-react-parser@npm:5.2.6" + version: 5.2.10 + resolution: "html-react-parser@npm:5.2.10" dependencies: domhandler: "npm:5.0.3" - html-dom-parser: "npm:5.1.1" + html-dom-parser: "npm:5.1.2" react-property: "npm:2.0.2" - style-to-js: "npm:1.1.17" + style-to-js: "npm:1.1.21" peerDependencies: "@types/react": 0.14 || 15 || 16 || 17 || 18 || 19 react: 0.14 || 15 || 16 || 17 || 18 || 19 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/be2903fd932d44ff6cae66bd18b025d318879e16eb1e67c0ae6dc640e5c77509da41b61948c0d404a43b5362b57b7cfa3963df242b9773d4c7a16f410db7f2ac + checksum: 10/3b0debac58e4b39d853731fd117b323a5d9582482cdc0b6dc5179d0c469acf63e64b8fdb3b678e758673633c579b09fd21d75d256c32104c65355f4434ba7f59 languageName: node linkType: hard @@ -3761,10 +3935,10 @@ __metadata: languageName: node linkType: hard -"inline-style-parser@npm:0.2.4": - version: 0.2.4 - resolution: "inline-style-parser@npm:0.2.4" - checksum: 10/80814479d1f3c9cbd102f9de4cd6558cf43cc2e48640e81c4371c3634f1e8b6dfeb2f21063cfa31d46cc83e834c20cd59ed9eeed9bfd45ef5bc02187ad941faf +"inline-style-parser@npm:0.2.7": + version: 0.2.7 + resolution: "inline-style-parser@npm:0.2.7" + checksum: 10/cdfe719bd694b53bfbad20a645a9a4dda89c3ff56ee2b95945ad4eea86c541501f49f852d23bc2f73cd8127b6b62ea9027f697838e323a7f7d0d9b760e27a632 languageName: node linkType: hard @@ -4179,13 +4353,13 @@ __metadata: linkType: hard "js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 + checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 languageName: node linkType: hard @@ -4275,92 +4449,102 @@ __metadata: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-darwin-arm64@npm:1.30.1" +"lightningcss-android-arm64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-android-arm64@npm:1.30.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-darwin-arm64@npm:1.30.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-darwin-x64@npm:1.30.1" +"lightningcss-darwin-x64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-darwin-x64@npm:1.30.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-freebsd-x64@npm:1.30.1" +"lightningcss-freebsd-x64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-freebsd-x64@npm:1.30.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.1" +"lightningcss-linux-arm-gnueabihf@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-linux-arm64-gnu@npm:1.30.1" +"lightningcss-linux-arm64-gnu@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm64-gnu@npm:1.30.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-linux-arm64-musl@npm:1.30.1" +"lightningcss-linux-arm64-musl@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm64-musl@npm:1.30.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-linux-x64-gnu@npm:1.30.1" +"lightningcss-linux-x64-gnu@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-x64-gnu@npm:1.30.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-linux-x64-musl@npm:1.30.1" +"lightningcss-linux-x64-musl@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-x64-musl@npm:1.30.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-win32-arm64-msvc@npm:1.30.1" +"lightningcss-win32-arm64-msvc@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-win32-arm64-msvc@npm:1.30.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.30.1": - version: 1.30.1 - resolution: "lightningcss-win32-x64-msvc@npm:1.30.1" +"lightningcss-win32-x64-msvc@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-win32-x64-msvc@npm:1.30.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.28.2": - version: 1.30.1 - resolution: "lightningcss@npm:1.30.1" + version: 1.30.2 + resolution: "lightningcss@npm:1.30.2" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-darwin-arm64: "npm:1.30.1" - lightningcss-darwin-x64: "npm:1.30.1" - lightningcss-freebsd-x64: "npm:1.30.1" - lightningcss-linux-arm-gnueabihf: "npm:1.30.1" - lightningcss-linux-arm64-gnu: "npm:1.30.1" - lightningcss-linux-arm64-musl: "npm:1.30.1" - lightningcss-linux-x64-gnu: "npm:1.30.1" - lightningcss-linux-x64-musl: "npm:1.30.1" - lightningcss-win32-arm64-msvc: "npm:1.30.1" - lightningcss-win32-x64-msvc: "npm:1.30.1" + lightningcss-android-arm64: "npm:1.30.2" + lightningcss-darwin-arm64: "npm:1.30.2" + lightningcss-darwin-x64: "npm:1.30.2" + lightningcss-freebsd-x64: "npm:1.30.2" + lightningcss-linux-arm-gnueabihf: "npm:1.30.2" + lightningcss-linux-arm64-gnu: "npm:1.30.2" + lightningcss-linux-arm64-musl: "npm:1.30.2" + lightningcss-linux-x64-gnu: "npm:1.30.2" + lightningcss-linux-x64-musl: "npm:1.30.2" + lightningcss-win32-arm64-msvc: "npm:1.30.2" + lightningcss-win32-x64-msvc: "npm:1.30.2" dependenciesMeta: + lightningcss-android-arm64: + optional: true lightningcss-darwin-arm64: optional: true lightningcss-darwin-x64: @@ -4381,7 +4565,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/6c921135216cc498dfcb87e35dffe8432e99306cbd58009c598b1daf20c81cc14535abbd4c1066e5d1faf4080ed44a2995e8ecc343633db4897a2d041b76fb05 + checksum: 10/d6cc06d9bac295589a49446e9c45a241dfa16f4f81a7318c26cbc0be3e189003ec0da5d9a0fd9bdffc63a3ce05878cc7329277eaac77a826e8b68c73dc96cfda languageName: node linkType: hard @@ -4401,13 +4585,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 - languageName: node - linkType: hard - "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -4558,8 +4735,8 @@ __metadata: linkType: hard "mdast-util-to-hast@npm:^13.0.0": - version: 13.2.0 - resolution: "mdast-util-to-hast@npm:13.2.0" + version: 13.2.1 + resolution: "mdast-util-to-hast@npm:13.2.1" dependencies: "@types/hast": "npm:^3.0.0" "@types/mdast": "npm:^4.0.0" @@ -4570,7 +4747,7 @@ __metadata: unist-util-position: "npm:^5.0.0" unist-util-visit: "npm:^5.0.0" vfile: "npm:^6.0.0" - checksum: 10/b17ee338f843af31a1c7a2ebf0df6f0b41c9380b7119a63ab521d271df665456578e1234bb7617883e8d860fe878038dcf2b76ab2f21e0f7451215a096d26cce + checksum: 10/8fddf5e66ea24dc85c8fe1cc2acd8fbe36e9d4f21b06322e156431fd71385eab9d2d767646f50276ca4ce3684cb967c4e226c60c3fff3428feb687ccb598fa39 languageName: node linkType: hard @@ -5071,12 +5248,12 @@ __metadata: languageName: node linkType: hard -"motion-dom@npm:^12.23.12": - version: 12.23.12 - resolution: "motion-dom@npm:12.23.12" +"motion-dom@npm:^12.23.23": + version: 12.23.23 + resolution: "motion-dom@npm:12.23.23" dependencies: motion-utils: "npm:^12.23.6" - checksum: 10/e13cbd1f32ad30dc642ebc83f7f35d0fb76c4d93902daebdbbea2cdb85bb07481fb4fe315b3e2c899076dab5490fb611ff803c334b336bc4fcd59b9a1aa6d7ff + checksum: 10/6bec34c28b478622bd23ce919c0d64e7f372cbc3f88e665f1e34fbf590e9bb53eebea086fb28f4e0c31941bd5274e086dc9b5360b68c4976891b94c4e92ca01a languageName: node linkType: hard @@ -5088,10 +5265,10 @@ __metadata: linkType: hard "motion@npm:^12.4.1": - version: 12.23.12 - resolution: "motion@npm:12.23.12" + version: 12.23.25 + resolution: "motion@npm:12.23.25" dependencies: - framer-motion: "npm:^12.23.12" + framer-motion: "npm:^12.23.25" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -5104,7 +5281,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/95670351c14d6925e891fac3d12719ed2e46000366abd38096870c46271071e3f22d0e9fa523809e614ee58aaeb1865fc19398dc8b408c2594512dc0eef392db + checksum: 10/67e86dd3c695362812fd86d06e82bb53173900212a78802c94adbcff947e70316141ec6e2908fb3ebf70d3686578bb7fbe7a820a359ce553cd2d79ff77fc1428 languageName: node linkType: hard @@ -5148,36 +5325,36 @@ __metadata: linkType: hard "next-mdx-remote-client@npm:^2.1.2": - version: 2.1.3 - resolution: "next-mdx-remote-client@npm:2.1.3" + version: 2.1.7 + resolution: "next-mdx-remote-client@npm:2.1.7" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@mdx-js/mdx": "npm:^3.1.0" - "@mdx-js/react": "npm:^3.1.0" - remark-mdx-remove-esm: "npm:^1.2.0" + "@mdx-js/mdx": "npm:^3.1.1" + "@mdx-js/react": "npm:^3.1.1" + remark-mdx-remove-esm: "npm:^1.2.1" serialize-error: "npm:^12.0.0" vfile: "npm:^6.0.3" vfile-matter: "npm:^5.0.1" peerDependencies: - react: ^19.1.0 - react-dom: ^19.1.0 - checksum: 10/3837c63edf7d707de2eea9b4c565eeb7e505a769e93c2673f746ec9993c644f7cd0aacb8bad911eff9ea63190f48c2c16ffe98d47c39591c5dac29c8bf9a9e0c + react: ^19.1 + react-dom: ^19.1 + checksum: 10/8aaf6c2f11b0ed398703165c35c0355d1f058521780191684e6fe7e45698effe50ba68b154bae9db84b13e82dff98f380224d588acca6e389aeb73062ecdd31d languageName: node linkType: hard -"next@npm:15.4.7": - version: 15.4.7 - resolution: "next@npm:15.4.7" +"next@npm:15.5.7": + version: 15.5.7 + resolution: "next@npm:15.5.7" dependencies: - "@next/env": "npm:15.4.7" - "@next/swc-darwin-arm64": "npm:15.4.7" - "@next/swc-darwin-x64": "npm:15.4.7" - "@next/swc-linux-arm64-gnu": "npm:15.4.7" - "@next/swc-linux-arm64-musl": "npm:15.4.7" - "@next/swc-linux-x64-gnu": "npm:15.4.7" - "@next/swc-linux-x64-musl": "npm:15.4.7" - "@next/swc-win32-arm64-msvc": "npm:15.4.7" - "@next/swc-win32-x64-msvc": "npm:15.4.7" + "@next/env": "npm:15.5.7" + "@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" + "@next/swc-linux-arm64-musl": "npm:15.5.7" + "@next/swc-linux-x64-gnu": "npm:15.5.7" + "@next/swc-linux-x64-musl": "npm:15.5.7" + "@next/swc-win32-arm64-msvc": "npm:15.5.7" + "@next/swc-win32-x64-msvc": "npm:15.5.7" "@swc/helpers": "npm:0.5.15" caniuse-lite: "npm:^1.0.30001579" postcss: "npm:8.4.31" @@ -5220,7 +5397,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/e611751247d5cfff9337a84979918bcd61919d7b66a411c982569336c3dba7e7a8704eead7e082e579807200fa8bc80602a3e2ce4423a8b5387aab3353400918 + checksum: 10/bfac0cbac41b36227ec91d3a0727561f73dfc35d6a78eafd0200528ef95fa2e9d2dba5a8c8922864b4f4e4321ae6a07c6cf8f5766ac3192ad03e68516c3a458a languageName: node linkType: hard @@ -5360,14 +5537,14 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:^4.3.3": - version: 4.3.3 - resolution: "oniguruma-to-es@npm:4.3.3" +"oniguruma-to-es@npm:^4.3.4": + version: 4.3.4 + resolution: "oniguruma-to-es@npm:4.3.4" dependencies: oniguruma-parser: "npm:^0.12.1" regex: "npm:^6.0.1" regex-recursion: "npm:^6.0.2" - checksum: 10/49b372569d335077c32bda066ac1da4a3f15dd25b717025cf43417fabd71d56e1152debcd8a832596e180d721b808822c88e65eb12abb26665ab8fe017f3c861 + checksum: 10/29be3f677cd948da7ea77c5ad62d0405daec58b015b4e4706c97d9f3dcd3a82a68a7314b90ec5dcd261a934aca9aa0bd8100180e2cbd252bc8a20e32fc9ab851 languageName: node linkType: hard @@ -5773,16 +5950,16 @@ __metadata: languageName: node linkType: hard -"remark-mdx-remove-esm@npm:^1.2.0": - version: 1.2.0 - resolution: "remark-mdx-remove-esm@npm:1.2.0" +"remark-mdx-remove-esm@npm:^1.2.1": + version: 1.2.1 + resolution: "remark-mdx-remove-esm@npm:1.2.1" dependencies: "@types/mdast": "npm:^4.0.4" mdast-util-mdxjs-esm: "npm:^2.0.1" unist-util-remove: "npm:^4.0.0" peerDependencies: unified: ^11 - checksum: 10/0a1155e08395ab73f877948455fc04d7457864f7556617efabecae7ea1b441cec2ca7ee9672e16a6cd56f2a16c57a4778f901e77d19052d1dd245de5bdf4618f + checksum: 10/092e95ca0649cfe08c47900d9922172b531e1d60b0f90955f51a7282620a1ae53a95dddde3f8d789cdd114a2d515a9372bc72b3e46200ff6f930bd89f0985982 languageName: node linkType: hard @@ -5821,10 +5998,10 @@ __metadata: languageName: node linkType: hard -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf +"reselect@npm:^5.1.1": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10/1fdae11a39ed9c8d85a24df19517c8372ee24fefea9cce3fae9eaad8e9cefbba5a3d4940c6fe31296b6addf76e035588c55798f7e6e147e1b7c0855f119e7fa5 languageName: node linkType: hard @@ -5928,15 +6105,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d - languageName: node - linkType: hard - "safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" @@ -6143,26 +6311,19 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.8.1": - version: 1.8.3 - resolution: "shell-quote@npm:1.8.3" - checksum: 10/5473e354637c2bd698911224129c9a8961697486cff1fb221f234d71c153fc377674029b0223d1d3c953a68d451d79366abfe53d1a0b46ee1f28eb9ade928f4c - languageName: node - linkType: hard - "shiki@npm:^3.13.0": - version: 3.13.0 - resolution: "shiki@npm:3.13.0" + version: 3.19.0 + resolution: "shiki@npm:3.19.0" dependencies: - "@shikijs/core": "npm:3.13.0" - "@shikijs/engine-javascript": "npm:3.13.0" - "@shikijs/engine-oniguruma": "npm:3.13.0" - "@shikijs/langs": "npm:3.13.0" - "@shikijs/themes": "npm:3.13.0" - "@shikijs/types": "npm:3.13.0" + "@shikijs/core": "npm:3.19.0" + "@shikijs/engine-javascript": "npm:3.19.0" + "@shikijs/engine-oniguruma": "npm:3.19.0" + "@shikijs/langs": "npm:3.19.0" + "@shikijs/themes": "npm:3.19.0" + "@shikijs/types": "npm:3.19.0" "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" - checksum: 10/8d7cd039ac8df548f724047b4ba81cd84c09e8b96f411eb7c781d3d105c96ff2ec6f64e1bac724fd6fc56ac2926322bc2466124ea279d04c5ef668d5a04219bb + checksum: 10/004dabc26a9db25c69f1aa6ed0555c8a5a34153084a1aa5f42fa03edf800ca13dd468ad6d7ba4cbf8f36dc8d316a7de29cecec228ea8d2d252f4c4c95eb162e6 languageName: node linkType: hard @@ -6286,13 +6447,6 @@ __metadata: languageName: node linkType: hard -"spawn-command@npm:0.0.2": - version: 0.0.2 - resolution: "spawn-command@npm:0.0.2" - checksum: 10/f13e8c3c63abd4a0b52fb567eba5f7940d480c5ed3ec61781d38a1850f179b1196c39e6efa2bbd301f82c1bf1cd7807abc8fbd8fc8e44bcaa3975a124c0d1657 - languageName: node - linkType: hard - "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -6337,7 +6491,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -6488,21 +6642,21 @@ __metadata: languageName: node linkType: hard -"style-to-js@npm:1.1.17, style-to-js@npm:^1.0.0": - version: 1.1.17 - resolution: "style-to-js@npm:1.1.17" +"style-to-js@npm:1.1.21, style-to-js@npm:^1.0.0": + version: 1.1.21 + resolution: "style-to-js@npm:1.1.21" dependencies: - style-to-object: "npm:1.0.9" - checksum: 10/431f2fca8a55a61939a83ff0f58638e2996621ad93a97cf93f2be5115f411330d4e506ccf18621bd45607ec161546b763bb6961ad08238ad939b6261ff377230 + style-to-object: "npm:1.0.14" + checksum: 10/5e30b4c52ed4e0294324adab2a43a0438b5495a77a72a6b1258637eebfc4dc8e0614f5ac7bf38a2f514879b3b448215d01fecf1f8d7468b8b95d90bed1d05d57 languageName: node linkType: hard -"style-to-object@npm:1.0.9": - version: 1.0.9 - resolution: "style-to-object@npm:1.0.9" +"style-to-object@npm:1.0.14": + version: 1.0.14 + resolution: "style-to-object@npm:1.0.14" dependencies: - inline-style-parser: "npm:0.2.4" - checksum: 10/fd0c131a83103fe4025afd8e0fd90c605054d485ad80f2ab402e7afa79f482f4b05fff40b6aa661cb1b835e5c56bb0644dc38cbf9b3d2982fc552435db3dae50 + inline-style-parser: "npm:0.2.7" + checksum: 10/06b86a5cf435dafac908d19082842983f9052d8cf3682915b1bd9251e3fe9b8065dbd2aef060dc5dfa0fa2ee24d717b587a5205f571513a10f30e3947f9d28ff languageName: node linkType: hard @@ -6531,15 +6685,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -6547,6 +6692,13 @@ __metadata: languageName: node linkType: hard +"tabbable@npm:^6.3.0": + version: 6.3.0 + resolution: "tabbable@npm:6.3.0" + checksum: 10/3e54a0b770d26bc20c3de5837652be19f5efa8bfa869f580af24bcf60de934506e9401a577213186b5e86ebcf6b5290a5429d354cc3041471815f5095e44e51a + languageName: node + linkType: hard + "tar@npm:^7.4.3": version: 7.4.3 resolution: "tar@npm:7.4.3" @@ -6594,15 +6746,6 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:^1.2.2": - version: 1.2.2 - resolution: "tree-kill@npm:1.2.2" - bin: - tree-kill: cli.js - checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 - languageName: node - linkType: hard - "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1" @@ -6638,7 +6781,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.0.1, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -6722,22 +6865,22 @@ __metadata: linkType: hard "typescript@npm:^5": - version: 5.9.2 - resolution: "typescript@npm:5.9.2" + version: 5.9.3 + resolution: "typescript@npm:5.9.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/cc2fe6c822819de5d453fa25aa9f32096bf70dde215d481faa1ad84a283dfb264e33988ed8f6d36bc803dd0b16dbe943efa311a798ef76d5b3892a05dfbfd628 + checksum: 10/c089d9d3da2729fd4ac517f9b0e0485914c4b3c26f80dc0cffcb5de1719a17951e92425d55db59515c1a7ddab65808466debb864d0d56dcf43f27007d0709594 languageName: node linkType: hard "typescript@patch:typescript@npm%3A^5#optional!builtin": - version: 5.9.2 - resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin::version=5.9.2&hash=5786d5" + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/bd810ab13e8e557225a8b5122370385440b933e4e077d5c7641a8afd207fdc8be9c346e3c678adba934b64e0e70b0acf5eef9493ea05170a48ce22bef845fdc7 + checksum: 10/696e1b017bc2635f4e0c94eb4435357701008e2f272f553d06e35b494b8ddc60aa221145e286c28ace0c89ee32827a28c2040e3a69bdc108b1a5dc8fb40b72e3 languageName: node linkType: hard @@ -6760,7 +6903,7 @@ __metadata: languageName: node linkType: hard -"unified@npm:^11.0.0": +"unified@npm:^11.0.0, unified@npm:^11.0.4": version: 11.0.5 resolution: "unified@npm:11.0.5" dependencies: @@ -6861,6 +7004,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.3 + resolution: "universal-user-agent@npm:7.0.3" + checksum: 10/c497e85f8b11eb8fa4dce584d7a39cc98710164959f494cafc3c269b51abb20fff269951838efd7424d15f6b3d001507f3cb8b52bb5676fdb642019dfd17e63e + languageName: node + linkType: hard + "unrs-resolver@npm:^1.6.2": version: 1.11.1 resolution: "unrs-resolver@npm:1.11.1" @@ -6937,6 +7087,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e + languageName: node + linkType: hard + "util@npm:^0.12.5": version: 0.12.5 resolution: "util@npm:0.12.5" @@ -7077,7 +7236,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -7121,13 +7280,6 @@ __metadata: languageName: node linkType: hard -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d - languageName: node - linkType: hard - "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -7151,28 +7303,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e - languageName: node - linkType: hard - -"yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 - languageName: node - linkType: hard - "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" diff --git a/docs/api/deprecations.md b/docs/api/deprecations.md index a9127426f0..84c0300543 100644 --- a/docs/api/deprecations.md +++ b/docs/api/deprecations.md @@ -124,7 +124,7 @@ createApiFactory({ ``` Provider specific factory implementations, copy the code you need into the -factory method depending on which apiRef you previously used. +factory method depending on which API ref you previously used. ```ts // samlAuthApiRef diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index fbb71221fc..3e60124775 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -18,23 +18,23 @@ plugins to communicate during their entire life cycle. ## Consuming APIs -Each Utility API is tied to an [`ApiRef`](../reference/core-plugin-api.apiref.md) +Each Utility API is tied to an [`ApiRef`](../reference/frontend-plugin-api.apiref.md) instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. -[`ApiRef`](../reference/core-plugin-api.apiref.md)s are created using -[`createApiRef`](../reference/core-plugin-api.createapiref.md), which is exported +[`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 many predefined Utility APIs in [`@backstage/core-plugin-api`](../reference/core-plugin-api.md), and they're all exported with a name of the pattern `*ApiRef`, for example -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md). +[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md). To access one of the Utility APIs inside a React component, use the -[`useApi`](../reference/core-plugin-api.useapi.md) hook exported by +[`useApi`](../reference/frontend-plugin-api.useapi.md) hook exported by [`@backstage/core-plugin-api`](../reference/core-plugin-api.md), or the -[`withApis`](../reference/core-plugin-api.withapis.md) HOC if you prefer class +[`withApis`](../reference/frontend-plugin-api.withapis.md) HOC if you prefer class components. For example, the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md) can be accessed like this: +[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md) can be accessed like this: ```tsx import { useApi, errorApiRef } from '@backstage/core-plugin-api'; @@ -52,9 +52,9 @@ export const MyComponent = () => { ``` Note that there is no explicit type given for -[`ErrorApi`](../reference/core-plugin-api.errorapi.md). This is because the -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) has the type -embedded, and [`useApi`](../reference/core-plugin-api.useapi.md) is able to infer +[`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 the type. Also note that consuming Utility APIs is not limited to plugins; it can be done @@ -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/core-plugin-api.apifactory.md) instances, which encapsulate +[`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) instances, which encapsulate the process of instantiating an API. It is a collection of three things: the -[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API to instantiate, a +[`ApiRef`](../reference/frontend-plugin-api.apiref.md) 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/core-plugin-api.apifactory.md) for the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md): +[`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) for the +[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md): ```ts createApiFactory({ @@ -89,25 +89,25 @@ createApiFactory({ }); ``` -In this example, the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) +In this example, the [`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) is our API, which encapsulates the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The -[`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single +[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md) type. The +[`alertApiRef`](../reference/frontend-plugin-api.alertapiref.md) 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/core-plugin-api.errorapi.md). +[`ErrorApi`](../reference/frontend-plugin-api.errorapi.md). -The [`createApiFactory`](../reference/core-plugin-api.createapifactory.md) +The [`createApiFactory`](../reference/frontend-plugin-api.createapifactory.md) 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/core-plugin-api.apiref.md)s. TypeScript will make sure +[`ApiRef`](../reference/frontend-plugin-api.apiref.md)s. TypeScript will make sure that the return value of the `factory` function matches the type embedded in -`api`'s [`ApiRef`](../reference/core-plugin-api.apiref.md), in this case the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md). It will also match the +`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 types between the `deps` and the parameters of the `factory` function, again using the type embedded within the -[`ApiRef`](../reference/core-plugin-api.apiref.md)s. +[`ApiRef`](../reference/frontend-plugin-api.apiref.md)s. ## Registering API Factories @@ -120,8 +120,8 @@ 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/core-plugin-api.errorapiref.md) and -[`configApiRef`](../reference/core-plugin-api.configapiref.md). +[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) and +[`configApiRef`](../reference/frontend-plugin-api.configapiref.md). The core APIs are loaded for any app created with [`createApp`](../reference/app-defaults.createapp.md) from @@ -133,7 +133,7 @@ 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/core-plugin-api.apifactory.md) of +supplies a default [`ApiFactory`](../reference/frontend-plugin-api.apifactory.md) of that API using the `CatalogClient`. There is one restriction to plugin-provided API Factories: plugins may not supply factories for core APIs; trying to do so will cause the app to refuse to start. @@ -227,16 +227,16 @@ const app = createApp({ ``` Note that the above line will cause an error if `IgnoreErrorApi` does not fully -implement the [`ErrorApi`](../reference/core-plugin-api.errorapi.md), as it is +implement the [`ErrorApi`](../reference/frontend-plugin-api.errorapi.md), as it is checked by the type embedded in the -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) at compile time. +[`errorApiRef`](../reference/frontend-plugin-api.errorapiref.md) 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/core-plugin-api.apiref.md) using -[`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from +[`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 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). @@ -244,16 +244,16 @@ for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's therefore possible to make breaking changes to the API without affecting other users of the plugin. If an API is made public, however, it opens up for other plugins to make use of the API, and it also makes it possible for users for your plugin to override the API in the app. It is, however, important to maintain backward compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the -[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated +[`ApiRef`](../reference/frontend-plugin-api.apiref.md) of the API, and any associated types. To make an API private, just avoid exporting the -[`ApiRef`](../reference/core-plugin-api.apiref.md), but still be sure to supply a +[`ApiRef`](../reference/frontend-plugin-api.apiref.md), but still be sure to supply a default factory to [`createPlugin`](../reference/core-plugin-api.createplugin.md). 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/core-plugin-api.createapiref.md), while public APIs +[`createApiRef`](../reference/frontend-plugin-api.createapiref.md), 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/core-plugin-api.apiref.md) instances mentioned above +The [`ApiRef`](../reference/frontend-plugin-api.apiref.md) 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/core-plugin-api.apiref.md), they are free to choose any +[`ApiRef`](../reference/frontend-plugin-api.apiref.md), they are free to choose any implementation they want. The figure below shows the relationship between diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 250736cec7..7c4b09d9bf 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -1,7 +1,7 @@ --- id: oidc title: OIDC provider from scratch -description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. +description: This section shows how to enable and use the Backstage OIDC provider. --- :::info @@ -11,78 +11,65 @@ system, you may want to read [its own article](https://github.com/backstage/back instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: -This section shows how to use an OIDC provider from scratch, same steps apply for custom -providers. Please note these steps are for using a provider, not how to implement one, -and Backstage recommends creating custom providers specific to the IDP, so we'll use a -`azureOIDC` provider throughout this example, feel free to change any of those refs -to your provider name. +This section shows how to enable and use the Backstage OIDC provider. ## Summary -To add providers not enabled by default like OIDC, we need to follow some steps, we -assume you already have a sign-in page to which we'll add the provider so users can -sign in through the provider. In simple steps here's how you enable the provider: +OIDC is a protocol which has numerous implementations. It's likely that many of your users won't know what the OIDC **protocol** is, but they will recognise your OIDC **implementation**. Backstage supplies a generic `oidc` authorization strategy. You should re-badge this with the name and branding of your OIDC implementation, so that your users will recognise it on the Backstage sign-in page. + +For example, if your organization uses [Keycloak](https://www.keycloak.org), you would re-badge the OIDC provider as `Keycloak` and tell users to `Sign In using Keycloak`. + +## Steps + +The Backstage OIDC provider is not enabled by default. You need to manually enable the provider, and tell it which OIDC server you want to use. + +To enable the Backstage OIDC provider: - Create an API reference to identify the provider. - Create the API factory that will handle the authentication. - Add or reuse an auth provider so you can authenticate. - Add or reuse a resolver to handle the result from the authentication. - Configure the provider to access your 3rd party auth solution. -- Add the provider to sign in page so users can login with it. +- Add the provider to the Backstage sign-in page. + +For simplicity, we assume that you only have a single OIDC provider in your Backstage installation. (If you need to have multiple OIDC providers in Backstage, the steps will be different.) We'll explain each step more in detail next. -### The API reference +### The API Reference -An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4] -for extended explanation. +An API reference exists to enable **Dependency Injection**. (See [Utility APIs][4] for an extended explanation.) -In this OIDC example, we'll create the API reference directly in the -`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this -file. Any location will do as long as it's available to be imported to where the API -factory is, as well as easily accessible to the rest of the application so any package -and plugin can inject the API instance when necessary. - -An example of such would be when you use an auth provider from a library installed with -NPM, or any other library repository, you would import the API ref from the library. +In this example, we'll create the API ref directly in the `packages/app/src/apis.ts` file. It is not a requirement to put the ref in this file. Any location will do as long as it's available to be imported to where the API factory is, as well as easily accessible to the rest of the application so any package and plugin can inject the API instance when necessary. ```ts -export const azureOIDCAuthApiRef: ApiRef< +export const keycloakAuthApiRef: ApiRef< OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi > = createApiRef({ - id: 'auth.my-custom-provider', + id: 'auth.keycloak', }); ``` -Please note a few things, the ID can be anything you want as long as it doesn't conflict -with other refs, backstage recommends to use a custom name that references your custom -provider, for example we are using OIDC protocol with Azure, so we could use something -like `auth.azure.oidc` as well. +The `id` of the API ref can be anything you want, as long as it doesn't conflict with other refs. Backstage recommends to use a custom name that references your custom provider. -Also we're exporting this reference, as well as the `typings`, we need to -be able to import this reference anywhere in the app, and the `typings` will tell typescript +:::note TypeScript Note +As we're exporting this API reference, as well as the TypeScript types, we need to +be able to import this reference anywhere in the app. The types will tell TypeScript what instance we're getting from DI when injecting the API. In this case we are defining an API for authentication, so we tell TS that this instance complies with 4 API interfaces: -- The OICD API that will handle authentication. +- The OIDC API that will handle authentication. - Profile API for requesting user profile info from the auth provider in question. - Backstage identity API to handle and associate the user profile with backstage identity. -- Session API, to handle the session the user will have while logged in. +- Session API, to handle the session the user will have while signed in. + ::: -### The API Factory +### The API Factory (and auth provider) -A factory is a function that can take some parameters or dependencies and return an -instance of something, in our case it will be a function that requests some backstage -APIs and use them to create an instance of an OIDC API provider. +The Backstage API factories are part of the Backstage Dependency Injection system. The factory function runs once, when something in your Backstage app first attempts to use an instance of the API it provides. The instance is then cached by the DI system for subsequent lookups. -Please note that this function only runs (creates the instance) when somewhere else in -the app you request the DI to give you an instance of the OIDC provider using the API ref -defined above, and the DI will only run this function the first time, from then on any -other DI injection will just receive the same instance created the first time, basically -the instance is cached by the DI library, a singleton. - -Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file: +Let's add a new API factory to the `apis` array in the `packages/app/src/apis.ts` file. We will tell it to use the OIDC auth provider internally. ```ts title="packages/app/src/apis.ts" /* highlight-add-next-line */ @@ -91,26 +78,29 @@ import { OAuth2 } from '@backstage/core-app-api'; export const apis: AnyApiFactory[] = [ /* highlight-add-start */ createApiFactory({ - api: azureOIDCAuthApiRef, + api: keycloakAuthApiRef, deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef, configApi: configApiRef, }, factory: ({ discoveryApi, oauthRequestApi, configApi }) => + // delegate auth to the OAuth2 strategy OAuth2.create({ configApi, discoveryApi, oauthRequestApi, provider: { - id: 'my-auth-provider', - title: 'My custom auth provider', + // this value MUST be 'oidc' + // it maps our Keycloak-branded sign-in provider onto Backstage's generic OIDC auth strategy + id: 'oidc', + title: 'Keycloak', icon: () => null, }, environment: configApi.getOptionalString('auth.environment'), defaultScopes: ['openid', 'profile', 'email'], popupOptions: { - // optional, used to customize login in popup size + // optional, used to customize sign-in window size size: { fullscreen: true, }, @@ -125,27 +115,14 @@ export const apis: AnyApiFactory[] = [ }), }), /* highlight-add-end */ - // .. ]; ``` -Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively -delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell -`OAuth2` to use the auth provider we'll define in the next section, and added the default -scopes to request ID, profile, email and user read permissions. - -## The Auth Provider - -The Auth Provider is responsible for authenticating with the 3rd party service, and give -us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, -OIDC, SAML or any other that your 3rd party IDP provider supports. - ### The Resolver -Resolvers exist to map user identity from the 3rd party (in this case an azure IDP -provider) to the backstage user identity. +Resolvers exist to map the user identity from the 3rd party (in this case Keycloak) to the Backstage user identity. -The default OIDC provider has built-in resolvers, here is how you configure them: +The default OIDC provider has a choice of built-in resolvers, here is how you configure them: ```yaml title="app-config.yaml" auth: @@ -159,7 +136,7 @@ auth: - resolver: emailMatchingUserEntityProfileEmail ``` -But you can also write a custom resolver as well, see an example below: +If none of the built-in resolvers are suitable, you can alternatively write a custom resolver. See an example below: ```ts title="in packages/backend/src/index.ts" /* highlight-add-start */ @@ -174,15 +151,15 @@ const myAuthProviderModule = 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: 'my-auth-provider', + moduleId: 'keycloak-auth-provider', register(reg) { reg.registerInit({ deps: { providers: authProvidersExtensionPoint }, async init({ providers }) { providers.registerProvider({ // This ID must match the actual provider config, e.g. addressing - // auth.providers.azure means that this must be "azure". - providerId: 'my-auth-provider', + // auth.providers.keycloak means that this must be "keycloak". + providerId: 'keycloak', // Use createProxyAuthProviderFactory instead if it's one of the proxy // based providers rather than an OAuth based one factory: createOAuthProviderFactory({ @@ -215,76 +192,85 @@ backend.add(myAuthProviderModule); //... ``` -For a more a detailed explanation about resolvers check the -[Identity Resolver][1] page. +For a more detailed explanation about resolvers check the [Identity Resolver][1] page. -### The configuration +### The Configuration -Since we are using our custom OIDC Auth Provider, we need to add a configuration based -on the provider used, in this case based on OIDC protocol (remember the 3rd party has to -support the protocol). +We will now configure our Keycloak-branded OIDC Auth Provider in Backstage, so that it can talk to our Keycloak server. -In this example we'll configure OIDC with `my-auth-provider`, to do so we need to -[Create app registration][2] in the Azure console, the only difference is that the -`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to -`http://localhost:7007/api/auth/my-auth-provider/handler/frame`. +The first step is to register an OIDC client app for Backstage in your Keycloak server. -Then we need to configure the env variables for the provider, based on the provider's code -in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables -in the `app-config.yaml`: +Then we need to configure the provider. Based on the provider's code in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following parameters in the `app-config.yaml`: ```yaml title="app-config.yaml" auth: environment: development - ### Providing an auth.session.secret will enable session support in the auth-backend session: - secret: ${SESSION_SECRET} + secret: ${AUTH_SESSION_SECRET} providers: - my-auth-provider: + oidc: development: metadataUrl: https://example.com/.well-known/openid-configuration - clientId: ${AUTH_MY_CLIENT_ID} - clientSecret: ${AUTH_MY_CLIENT_SECRET} + clientId: ${AUTH_OIDC_CLIENT_ID} + clientSecret: ${AUTH_OIDC_CLIENT_SECRET} ``` -Anything enclosed in `${}` can be replaced directly in the yaml, or provided as -environment variables, the way you obtain all these except `scope` and `prompt` is to -check the App Registration you created: +Anything enclosed in `${}` can be replaced directly in the YAML, or provided as environment variables. + +#### Required Parameters + +These parameters must always be set. - `clientId`: Grab from the Overview page. - `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a new secret. - `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL. + +The OIDC provider **also** requires the `auth.session.secret` to be set. + +#### Optional Parameters + +These parameters have implicit default values. Don't override them unless you know what you're doing. + - `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will hold these 2 urls somewhere in there. -- `tokenEndpointAuthMethod`: Don't define it, use the default unless you know what it does. -- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does. +- `tokenEndpointAuthMethod` +- `tokenSignedResponseAlg` - `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory, basically the same thing. -- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the +- `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the user has no active session. -- `sessionDuration` (optional): Lifespan of the user session. +- `sessionDuration`: Lifespan of the user session. -Note that for the time being, any change in this yaml file requires a restart of the app, -also you need to have the `session.secret` part to use OIDC (some other providers might -need this as well) to support user sessions. +:::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. +::: -### The Sign In provider +### The Sign-In Page -The last step is to add the provider to the `SignInPage` so users can sign in with your -new provider, please follow the [Sign In Configuration][3] docs, here's where you import -and use the API reference we defined earlier. +The last step is to add the provider to the sign-in page, so users can sign in with your new provider. + +If you are using the standard Backstage [`SignInPage`][3] component, you can just add it to the `providers` array like this: + +```ts title="in packages/app/src/identityProviders.ts" +export const providers = [ + // other providers... + { + id: 'keycloak-auth-provider', + title: 'Keycloak', + message: 'Sign In using Keycloak', + apiRef: keycloakAuthApiRef, + }, +]; +``` :::note Note - -These steps apply to most if not all the providers, including custom providers, the main -difference between different providers will be the contents of the API factory, the code +These steps apply to most auth providers. The main +difference between providers will be the contents of the API factory, the code in the Auth Provider Factory, the resolver, and the different variables each provider needs in the YAML config or env variables. - ::: [1]: https://backstage.io/docs/auth/identity-resolver -[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure [3]: https://backstage.io/docs/auth/#sign-in-configuration [4]: https://backstage.io/docs/api/utility-apis diff --git a/docs/backend-system/architecture/02-backends.md b/docs/backend-system/architecture/02-backends.md index 8f903eb7a8..4c9857df86 100644 --- a/docs/backend-system/architecture/02-backends.md +++ b/docs/backend-system/architecture/02-backends.md @@ -38,3 +38,28 @@ Once you have added all desired features we call the `.start()` method. This cau Underneath the hood, `createBackend` calls `createSpecializedBackend` from `@backstage/backend-app-api` which is responsible for actually creating the backend instance, without any services or features. You can think of `createBackend` more of a 'batteries included' approach, while `createSpecializedBackend` is more low level. As mentioned previously there's also the ability to create multiple of these backends in your project so that you can split apart your backend and deploy different backends that can scale independently of each other. For instance you might choose to deploy a backend with only the catalog plugin enabled, and one with just the scaffolder plugin enabled. + +### Backend Startup Result + +The `Backend.start()` method 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. + +```ts +backend.start( + ({ result }) => { + console.log(`Backend startup result: ${JSON.stringify(result, null, 2)}`); + }, + error => { + if (error instanceof BackendStartupError) { + console.error( + `Backend startup failed: ${JSON.stringify(error.result, null, 2)}`, + ); + } else { + console.error( + `Unexpected error during backend startup: ${error.message}`, + ); + } + }, +); +``` + +This information is mostly useful if you want to add additional monitoring or debugging tools to your backend. The information is a structured representation of what is already logged during startup. diff --git a/docs/backend-system/core-services/root-instance-metadata.md b/docs/backend-system/core-services/root-instance-metadata.md new file mode 100644 index 0000000000..109042f1a5 --- /dev/null +++ b/docs/backend-system/core-services/root-instance-metadata.md @@ -0,0 +1,44 @@ +--- +id: root-instance-metadata +title: Root Instance Metadata Service +sidebar_label: Root Instance Metadata +description: Documentation for the Root Instance Metadata service +--- + +The root instance metadata service provides information about the running Backstage backend instance. Currently, it provides a list of all installed backend plugins. + +:::note Note + +The root instance metadata service only provides information about the specific Backstage instance you're running on. In more complex deployments with multiple Backstage instances, this service will not provide a complete list of all plugins across all instances. + +::: + +## Using the service + +The following example shows how to use the root instance metadata service in your `example` backend plugin to access the list of installed backend plugins. + +```ts +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +createBackendPlugin({ + pluginId: 'example', + register(env) { + env.registerInit({ + deps: { + instanceMetadata: coreServices.rootInstanceMetadata, + }, + async init({ instanceMetadata }) { + const plugins = instanceMetadata.getInstalledPlugins(); + console.log('Installed plugins:', plugins); + }, + }); + }, +}); +``` + +## Dynamic plugin registration + +The root instance metadata service picks up plugins that are registered at start time through a `backend.start()` call. You need to restart the running backend instance to pick up newly installed plugins. diff --git a/docs/conf/reading.md b/docs/conf/reading.md index 61cff2502b..a565f3a8a3 100644 --- a/docs/conf/reading.md +++ b/docs/conf/reading.md @@ -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/core-plugin-api.configapi.md) in the frontend is a +The [ConfigApi](../reference/frontend-plugin-api.configapi.md) 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/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index a692335ffe..bbf35557ce 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -48,6 +48,65 @@ add the `repoVisibility` key within a software template: repoVisibility: public # or 'internal' or 'private' ``` +### Default Environment + +The scaffolder supports a `defaultEnvironment` configuration that provides default parameters and secrets to all templates. This reduces template complexity and improves security by centralizing common values. + +```yaml +scaffolder: + defaultEnvironment: + parameters: + region: eu-west-1 + organizationName: acme-corp + defaultRegistry: registry.acme-corp.com + secrets: + AWS_ACCESS_KEY: ${AWS_ACCESS_KEY} + GITHUB_TOKEN: ${GITHUB_TOKEN} + DOCKER_REGISTRY_TOKEN: ${DOCKER_REGISTRY_TOKEN} +``` + +#### Default parameters + +Default parameters are accessible via `${{ environment.parameters.* }}` in templates. Default parameters are isolated in their own context to avoid naming conflicts. + +```yaml + parameters: + - title: Fill in some steps + required: + - organizationName + properties: + organizationName: + title: organizationName + type: string + description: Unique name of the organization + ui:autofocus: true + ui:options: + rows: 5 + + steps: + - id: deploy + name: Deploy Application + action: aws:deploy + input: + region: ${{ environment.parameters.region }} # Resolves to defaultEnvironment.parameters.region + organization: ${{ parameters.organizationName }} # Resolves to frontend input value + otherOrganization: ${{ environment.parameters.organizationName }} # Resolves to defaultEnvironment.parameters.organizationName +``` + +#### Secrets + +Default secrets are resolved from environment variables and accessible via `${{ environment.secrets.* }}` in template actions. Secrets are only available during action execution, not in frontend forms. + +```yaml +- id: deploy + name: Deploy with credentials + action: aws:deploy + input: + accessKey: ${{ environment.secrets.AWS_ACCESS_KEY }} # Resolves to defaultEnvironment.secrets.AWS_ACCESS_KEY +``` + +**Security Note:** Secrets are automatically masked in logs and are only available to backend actions, never exposed to the frontend. + ## Disabling Docker in Docker situation (Optional) Software templates use the `fetch:template` action by default, which requires no diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index 24a5f24e12..a6d00a3131 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -352,3 +352,69 @@ const extension = createExtension({ }, }); ``` + +## Relative attachment points + +When creating an extension or an [extension blueprint](./23-extension-blueprints.md) you can specify an attachment point that is relative to the current plugin. This is particularly useful for groups of blueprints that are part of a common hierarchy, with extensions from one blueprint attaching to extensions from the other blueprint. For example, the following pair of extension definitions could be installed multiple times in different plugins, each creating their own hierarchy: + +```tsx +// Parent extension with a fixed attachment point +const parentExtension = createExtension({ + kind: 'section', + attachTo: [{ id: 'app/some-fixed-extension', input: 'children' }], + inputs: { + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( +
+

Section Title

+ {inputs.content.get(coreExtensionData.reactElement)} +
, + ), + ]; + }, +}); + +// Child extension with a relative attachment point +const childExtension = createExtension({ + kind: 'section-content', + attachTo: [{ relative: { kind: 'section' }, input: 'content' }], + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(

Section Content

)]; + }, +}); +``` + +## Extension input references + +Building on the relative attachment point concept, you can also reference extension inputs directly via the `inputs` property of an extension definition. This provides a more convenient and type-safe way to attach child extensions, especially when using blueprints that provide a nested hierarchy of extensions. + +Extension inputs references are always relative, this means that they can only be used for referencing extensions within the same plugin. + +Each extension definition exposes an `inputs` property that contains references to all of its defined inputs. These references can be passed directly to the `attachTo` option when creating child extensions: + +```tsx +const parent = createExtension({ + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + // other options... +}); + +// Create a child extension that attaches to the parent's input +const child = createExtension({ + attachTo: page.inputs.children, // Direct reference to the input + output: [coreExtensionData.reactElement], // Outputs are verified against the parent input + // other options... +}); +``` + +These references are a type-safe way to attach child extensions, it both ensures that the parent input is present, as well as the child providing the required data for the parent. + +Under the hood, input references are resolved in the same way as relative attachment points, using the extension's kind, namespace, and name to construct the final attachment target. diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index 0c96bdf15d..ed84aeafb2 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -14,16 +14,16 @@ A Backstage App is a monorepo setup that includes everything you need to run Bac To create a new Backstage app we recommend using the `@backstage/create-app` command line, and the easiest way to run this package is with `npx`: :::note -The create-app CLI requires Node.js Active LTS Release. +The create-app CLI requires Node.js Active LTS Release, see the [prerequisites documentation](../../getting-started/index.md) for all the details. ::: ```sh # The command bellow creates a Backstage App inside the current folder. # The name of the app-folder is the name that was provided when prompted. -npx @backstage/create-app@latest +npx @backstage/create-app@latest --next ``` -The created-app is currently templated for legacy frontend system applications, so the app wiring code it creates needs to be migrated, see [the app instance](#the-app-instance) section for an example. +Using the `--next` flag will result in a Backstage app using the New Frontend System which will be further explained in the sections below. ## The app instance diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index f57c22db96..1fd74becfc 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -9,7 +9,7 @@ description: How to migrate existing apps to the new frontend system This section describes how to migrate an existing Backstage app package to use the new frontend system. The app package is typically found at `packages/app` in your project and is responsible for wiring together the Backstage frontend application. -> **Who is this for?** +> **Who is this for?** > This guide is intended for maintainers of Backstage app packages (`packages/app`) who want to upgrade from the legacy frontend system to the new extension-based architecture. > **Prerequisites:** @@ -22,10 +22,10 @@ This section describes how to migrate an existing Backstage app package to use t We recommend a **two-phase migration process** to ensure a smooth and manageable transition: -- **Phase 1: Minimal Changes for Hybrid Configuration** +- **Phase 1: Minimal Changes for Hybrid Configuration** In this phase, you make the smallest set of changes necessary to enable your app to run in a hybrid mode. This allows you to start using the new frontend system while still relying on compatibility helpers and legacy code. The goal is to unblock your migration quickly, so you can benefit from the new system without a full rewrite. -- **Phase 2: Complete Transition to the New Frontend System** +- **Phase 2: Complete Transition to the New Frontend System** After your app is running in hybrid mode, you can gradually refactor your codebase to remove legacy code and compatibility helpers. This phase focuses on fully adopting the new frontend architecture, ensuring your codebase is clean, maintainable, and takes full advantage of the new features. :::warning @@ -157,36 +157,6 @@ const app = createApp({ }); ``` -If you were binding routes from a legacy `createApp`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. - -For example, if both the `catalogPlugin` and `scaffolderPlugin` are legacy plugins, you can bind their routes like this: - -```ts -import { createApp } from '@backstage/frontend-defaults'; -import { - // ... - convertLegacyRouteRefs, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; - -// Ommitting converted options changes -//... - -const app = createApp({ - features: [ - // ... - convertedOptionsModule, - ], - // highlight-add-start - bindRoutes({ bind }) { - bind(convertLegacyRouteRefs(catalogPlugin.externalRoutes), { - createComponent: convertLegacyRouteRef(scaffolderPlugin.routes.root), - }); - }, - // highlight-add-end -}); -``` - ### 3) Fixing the `app.createRoot` call The `app.createRoot(...)` no longer accepts any arguments. This represents a fundamental change that the new frontend system introduces. In the old system the app element tree that you passed to `app.createRoot(...)` was the primary way that you installed and configured plugins and features in your app. In the new system this is instead replaced by extensions that are wired together into an extension tree. Much more responsibility has now been shifted to plugins, for example you no longer have to manually provide the route path for each plugin page, but instead only configure it if you want to override the default. For more information on how the new system works, see the [architecture](../architecture/00-index.md) section. @@ -590,21 +560,6 @@ const app = createApp({ Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/36-routes.md#binding-external-route-references) for more information. -Note that if you are binding routes from a legacy plugin that was converted using `convertLegacyAppRoot`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. - -For example, if both the `catalogPlugin` and `scaffolderPlugin` are legacy plugins, you can bind their routes like this: - -```ts -const app = createApp({ - features: convertLegacyAppRoot(...), - bindRoutes({ bind }) { - bind(convertLegacyRouteRefs(catalogPlugin.externalRoutes), { - createComponent: convertLegacyRouteRef(scaffolderPlugin.routes.root), - }); - }, -}); -``` - #### `__experimentalTranslations` Translations are now installed as extensions, created using `TranslationBlueprint`. @@ -721,8 +676,6 @@ createApp({ AppRootWrapperBlueprint.make({ name: 'custom-app-barrier', params: { - // Whenever your component uses legacy core packages, wrap it with "compatWrapper" - // e.g. props => compatWrapper() Component: CustomAppBarrier, }, }), @@ -745,41 +698,37 @@ import { SidebarContent } from './Sidebar'; export const navModule = createFrontendModule({ pluginId: 'app', - extensions: [SidebarContent], + extensions: [sidebarContent], }); ``` -Then in the actual implementation for the `SidebarContent` extension, you can provide something like the following, where the component that is passed to the `compatWrapper` is the entire `Sidebar` component from your `Root` component. - -The `compatWrapper` is there to ensure that any legacy plugins using things like `useRouteRef` work well in the new system, so if you run into some errors which look like compatibility issues, make sure that this wrapper is used in the relevant places. +Then in the actual implementation for the `sidebarContent` extension, you can provide something like the following, where you implement the entire `Sidebar` component. ```tsx title="in packages/app/src/modules/nav/Sidebar.tsx" -import { compatWrapper } from '@backstage/core-compat-api'; import { NavContentBlueprint } from '@backstage/frontend-plugin-api'; -export const SidebarContent = NavContentBlueprint.make({ +export const sidebarContent = NavContentBlueprint.make({ params: { - component: ({ items }) => - compatWrapper( - - - } to="/search"> - - - - }> - ... - - - - {/* Items in this group will be scrollable if they run out of space */} - {items.map((item, index) => ( - - ))} - - - , - ), + component: ({ items }) => ( + + + } to="/search"> + + + + }> + ... + + + + {/* Items in this group will be scrollable if they run out of space */} + {items.map((item, index) => ( + + ))} + + + + ), }, }); ``` diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 2d954cb526..b0e9816a17 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -38,7 +38,6 @@ In order to migrate the actual definition of the plugin you need to recreate the ```ts title="my-plugin/src/alpha.tsx" import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; - import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; export default createFrontendPlugin({ // The plugin ID is now provided as `pluginId` instead of `id` @@ -47,15 +46,12 @@ In order to migrate the actual definition of the plugin you need to recreate the // bind all the extensions to the plugin /* highlight-next-line */ extensions: [/* APIs will go here, but don't worry about those yet */], - // convert old route refs to the new system - /* highlight-next-line */ - routes: convertLegacyRouteRefs({ + routes: { ... - }), - /* highlight-next-line */ - externalRoutes: convertLegacyRouteRefs({ + }, + externalRoutes: { ... - }), + }, }); ``` @@ -110,25 +106,16 @@ it can be migrated as the following, keeping in mind that you may need to switch ```tsx import { PageBlueprint } from '@backstage/frontend-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; const fooPage = PageBlueprint.make({ params: { // This is the path that was previously defined in the app code. // It's labelled as the default one because it can be changed via configuration. path: '/foo', - // You can reuse the existing routeRef by wrapping it with convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootRouteRef), + // You can reuse the existing routeRef. + routeRef: rootRouteRef, // these inputs usually match the props required by the component. - loader: () => - import('./components/').then(m => - // The compatWrapper utility allows you to keep using @backstage/core-plugin-api in the - // implementation of the component and switch to @backstage/frontend-plugin-api later. - compatWrapper(), - ), + loader: () => import('./components/').then(m => ), }, }); ``` diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md index f0a032353c..6b735171a4 100644 --- a/docs/integrations/bitbucketCloud/locations.md +++ b/docs/integrations/bitbucketCloud/locations.md @@ -14,11 +14,22 @@ plugin. ## Configuration +API token usage example (recommended): + ```yaml integrations: bitbucketCloud: - - username: ${BITBUCKET_CLOUD_USERNAME} - appPassword: ${BITBUCKET_CLOUD_PASSWORD} + - username: user@domain.com # username -> user email + token: my-token +``` + +Legacy: + +```yaml +integrations: + bitbucketCloud: + - username: username + appPassword: my-password ``` :::note Note @@ -30,7 +41,7 @@ convenience, so you only need to list it if you want to supply credentials. :::note Note -The credential used for this is type [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/) or an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work. ::: @@ -42,4 +53,5 @@ This one entry will have the following elements: - `username`: The Bitbucket Cloud username to use in API requests. If 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. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index e01729dff0..f245e960ea 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -210,6 +210,8 @@ catalog: filters: # optional filters branch: 'develop' # optional string repository: '.*' # optional Regex + pageSizes: + repositories: 25 wildcardProviderId: organization: 'new-org' # string catalogPath: '/groups/**/*.yaml' # this will search all folders for files that end in .yaml @@ -308,6 +310,10 @@ If you do so, `default` will be used as provider ID. The amount of time that should pass before the first invocation happens. - **`scope`** _(optional)_: `'global'` or `'local'`. Sets the scope of concurrency control. +- **`pageSizes`** _(optional)_: + Configure page sizes for GitHub GraphQL API queries. This can help prevent `RESOURCE_LIMITS_EXCEEDED` errors. + - **`repositories`** _(optional)_: + Number of repositories to fetch per page. Defaults to `25`. Reduce this value if hitting API resource limits. ## GitHub API Rate Limits diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 473d408b0b..284451af8c 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -79,6 +79,10 @@ catalog: initialDelay: { seconds: 30 } frequency: { hours: 1 } timeout: { minutes: 50 } + pageSizes: + teams: 25 + teamMembers: 50 + organizationMembers: 50 - id: ghe githubUrl: https://ghe.mycompany.com orgs: ['internal-1', 'internal-2', 'internal-3'] @@ -86,6 +90,7 @@ catalog: initialDelay: { seconds: 30 } frequency: { hours: 1 } timeout: { minutes: 50 } + excludeSuspendedUsers: true ``` Directly under the `githubOrg` is a list of configurations, each entry is a structure with the following elements: @@ -94,6 +99,15 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru - `githubUrl`: The target that this provider should consume - `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). - `schedule`: The refresh schedule to use, matches the structure of [`SchedulerServiceTaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinitionconfig/) +- `pageSizes` (optional): Configure page sizes for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors. You can configure the following page sizes: + + - `teams`: Number of teams to fetch per page when querying organization teams (default: 25) + - `teamMembers`: Number of team members to fetch per page when querying team members (default: 50) + - `organizationMembers`: Number of organization members to fetch per page (default: 50) + + Reducing page sizes will result in more API calls and slightly longer sync times, but will prevent API resource limits for organizations with large number of teams and members. + +- `excludeSuspendedUsers` (optional): Whether to exclude suspended users when querying organization users. Only for GitHub Enterprise instances. Will error if used against GitHub.com API. ### Events Support diff --git a/docs/notifications/index.md b/docs/notifications/index.md index 59ff2962ed..dce58d3214 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -164,6 +164,61 @@ You can customize the origin names shown in the UI by passing an object where th Each notification processor will receive its own row in the settings page, where the user can enable or disable notifications from that processor. +### Default notification settings + +You can configure default notification settings for all users in your `app-config.yaml` file. This allows you to set up notification preferences globally, such as disabling specific channels or origins by default, implementing an opt-in strategy instead of opt-out. + +#### Channel-level defaults + +You can set a default enabled state for an entire channel. When set to `false`, the channel uses an opt-in strategy where notifications are disabled by default unless explicitly enabled by the user or for specific origins. + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Web' + enabled: false # Opt-in strategy: channel disabled by default + - id: 'Email' + enabled: true # Opt-out strategy: channel enabled by default (default behavior) +``` + +#### Origin-level defaults + +You can also configure defaults for specific origins within a channel: + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Web' + enabled: true # Channel is enabled by default + origins: + - id: 'plugin:scaffolder' + enabled: false # Disable scaffolder notifications by default + - id: 'plugin:catalog' + enabled: true # Enable catalog notifications by default +``` + +#### Topic-level defaults + +For even more granular control, you can set defaults for specific topics within origins: + +```yaml +notifications: + defaultSettings: + channels: + - id: 'Email' + enabled: false # Email is opt-in by default + origins: + - id: 'plugin:catalog' + enabled: true # But catalog notifications are enabled + topics: + - id: 'entity:validation:error' + enabled: false # Except validation errors +``` + +**Note:** If a channel's `enabled` flag is not set, it defaults to `true` for backwards compatibility. When a channel is set to `enabled: false`, all origins within that channel default to disabled unless explicitly enabled. + ### Automatic notification cleanup Notifications are deleted automatically after a certain period of time to prevent the database from growing indefinitely diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 669a982e1a..420b6489ae 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -177,7 +177,7 @@ When we say _Supporting_ a Node.js release, that means the following: - New Backstage projects created with `@backstage/create-app` will have their `engines.node` version set accordingly. - Dropping compatibility with unsupported releases is not considered a breaking change. This includes using new syntax or APIs, as well as bumping dependencies that drop support for these versions. -Based on the above Backstage supports Node.js 20 and 22 as of the `1.33.0` release. +Based on the above Backstage supports Node.js 22 and 24 as of the `1.46.0` release. ## TypeScript Releases diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index fa1c75de44..024bea8f81 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -39,8 +39,6 @@ Out of the box, Backstage includes: Backstage is a CNCF Incubation project after graduating from Sandbox. Read the announcement [here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). -CNCF logo - ## Benefits - For _engineering managers_, it allows you to maintain standards and best diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 7bfcd1220a..d90f36e59d 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -159,7 +159,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer ```typescript title="packages/backend/src/modules/catalogPermissionRules.ts" import { createBackendModule } from '@backstage/backend-plugin-api'; import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; - import { isInSystemRule } from './permissionPolicyExtension'; + import { isInSystemRule } from './permissionsPolicyExtension'; export default createBackendModule({ pluginId: 'catalog', diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index bfd210aeb9..9b8b63fc89 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -48,11 +48,20 @@ This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run ```sh -curl localhost:7007/api/carmen/health +curl localhost:7007/api/carmen/todos ``` -This should return `{"status":"ok"}`. Success! Press `Ctrl + c` to stop it -again. +You should see the following response: + +```json +{ + "items": [] +} +``` + +:::note Note: The route shown here matches the default in the current backend plugin template. If you want a `/health` endpoint for health checks, you can add it to your router yourself. + +::: ## Developing your Backend Plugin diff --git a/docs/publishing.md b/docs/publishing.md index e6c0b6b466..15b613f800 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -69,17 +69,23 @@ Once the release has been published edit the newly created release in the [GitHu **This emergency release process is intended only for the Backstage maintainers.** -Given one or more PRs towards master that we want to create a patch release for, run the following script from the repo root: +Given one or more PRs towards master that we want to create a patch release for, add them to the patch release queue by running the following command for each PR: ```bash -./scripts/patch-release-for-pr.js ... +yarn patch-pr ``` -Wait until the script has finished executing, at the end of the output you will find a link of the format `https://github.com/backstage/backstage/pull/new/patch-release-pr-...`. Open this link in your browser to create a PR for the patch release. Finish the sentence "This release fixes an issue where..." and create the PR. +This creates a patch file in the `.patches/` directory (e.g., `.patches/pr-12345.txt`) containing the description of the fix. The [sync_patch-release.yml](https://github.com/backstage/backstage/blob/master/.github/workflows/sync_patch-release.yml) workflow will automatically detect these patch files and create or update a "Patch Release" PR. -Once the PR has been approved and merged, the patch release will be automatically created. The patch release is complete when a notification has been posted to Discord in the `#announcements` channel. Keep an eye on "Deploy Packages" workflow and re-trigger if it fails. It is safe to re-trigger any part of this workflow, including the release step. +The workflow will: -If the above process fails, you can fall back to the manual process documented below. +- Automatically create a patch release PR if one doesn't exist +- Update the existing PR if patch files are added, modified, or removed +- Close and delete the PR branch if all patch files are removed + +Once the "Patch Release" PR has been approved and merged, the patch release will be automatically created. The patch files will be automatically removed from the master branch after the patch release is merged. From here the patch release process is the same as the usual release process, starting with the notification in the `#maintainers` channel on Discord. + +If the above process fails, you can fall back to the manual process documented below, or run the `./scripts/patch-release-for-pr.js ...` script to manually create a patch release PR. ### Old Process diff --git a/docs/releases/v1.45.0-changelog.md b/docs/releases/v1.45.0-changelog.md new file mode 100644 index 0000000000..23def9a969 --- /dev/null +++ b/docs/releases/v1.45.0-changelog.md @@ -0,0 +1,2607 @@ +# Release v1.45.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.45.0](https://backstage.github.io/upgrade-helper/?to=1.45.0) + +## @backstage/backend-app-api@1.3.0 + +### Minor Changes + +- a17d9df: Updates API for `instanceMetadata` service to return a list of plugins not features. + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + +## @backstage/backend-plugin-api@1.5.0 + +### Minor Changes + +- a17d9df: Promote `instanceMetadata` service to main entrypoint. +- 62fc2de: Explicitly mark `coreServices.rootInstanceMetadata` as a root service. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/backend-test-utils@1.10.0 + +### Minor Changes + +- d57b13b: Added support for Postgres 18 to the available `TestDatabases`. + + Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/core-plugin-api@1.12.0 + +### Minor Changes + +- 83439b1: All route references are now forwards compatible with the new frontend system, i.e. `@backstage/frontend-plugin-api`. This means they no longer need to be converted with `convertLegacyRouteRef` or `convertLegacyRouteRefs` from `@backstage/core-compat-api`. + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/eslint-plugin@0.2.0 + +### Minor Changes + +- 926389b: Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. + +## @backstage/frontend-plugin-api@0.13.0 + +### Minor Changes + +- 7d87b4f: Renamed `ExtensionDefinition` to `OverridableExtensionDefinition` and introduced a slimmer `ExtensionDefinition` type that does not include override methods. The overridable type is generally used as an output type, while plain `ExtensionDefinition`s are used for input. This reduces type conflicts across different of `@backstage/frontend-plugin-api`, improving long-term compatibility. + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. + +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/repo-tools@0.16.0 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## @backstage/ui@0.9.0 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + Migration: + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + Migration examples: + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. + +- a67670d: **BREAKING**: Removed central `componentDefinitions` object and related type utilities (`ComponentDefinitionName`, `ComponentClassNames`). + + Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. + + **Migration Guide:** + + If you were using component definitions or class names to build custom components, we recommend migrating to either: + + - Use Backstage UI components directly as building blocks, or + - Duplicate the component CSS in your own stylesheets instead of relying on internal class names + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. +- 26c6a78: Fix default text color in Backstage UI +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. +- 1059f95: Improved the Link component structure in Backstage UI. +- 836b0c7: Fixed dialog backdrop appearance in dark mode. +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- dac851f: Fix the default font size in Backstage UI. +- 3c0ea67: Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. +- 4eb455c: Fix font smoothing as default in Backstage UI. +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the `href` prop. +- e16ece5: Set the color-scheme property depending on theme +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. +- 00bfb83: Fix default font wight and font family in Backstage UI. + +## @backstage/plugin-catalog@1.32.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- b500bcc: Fix React warnings for elements not having stable keys. +- a87c0a5: Fixed responsiveness issues with catalog entity content layout using the new frontend system. +- aff04d3: Fixed missing margins for entity warning content area on catalog entity page in NFS. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-catalog-backend@3.2.0 + +### Minor Changes + +- 2d229b2: Enable YAML merge keys in yamlPlaceholderResolver +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.0 + +### Minor Changes + +- 980f240: Moved from `ldapjs` dependency to `ldapts` + + ### Breaking Changes + + **Type Migration** + + Custom transformers must now accept `Entry` from `ldapts` instead of `SearchEntry` + from `ldapjs` The Entry type provides direct property access without need for + `.object()` or `.raw()` methods. + + If you have custom user or group transformers, update the signature from: + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: SearchEntry) => + Promise; + ``` + + to + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: Entry) => + Promise; + ``` + + **Search Options** + + Updated LDAP search configuration `typesOnly: false` → `attributeValues: true` + This inverts the boolean logic: `ldapjs` used negative form while `ldapts` uses + positive form. Both achieve the same result: retrieving attribute values rather + than just attribute names. + + Update LDAP search options in configuration from + + ```yaml + options: + typesOnly: false + ``` + + to + + ```yaml + options: + attributeValues: true + ``` + + **API Changes** Removed `LdapClient.searchStreaming()` method. Users should + migrate to `LdapClient.search()` instead + + If you're using `searchStreaming` directly: + + ```typescript + // Before + await client.searchStreaming(dn, options, async entry => { + // process each entry + }); + + // After + const entries = await client.search(dn, options); + for (const entry of entries) { + // process each entry + } + ``` + + > **_NOTE:_**: Both methods have always loaded all entries into memory. The + > `searchStreaming` method was only needed internally to handle `ldapjs`'s + > event-based API. + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-node@1.20.0 + +### Minor Changes + +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-gateway-backend@1.1.0 + +### Minor Changes + +- 229f63e: Added hop count tracking to prevent proxy loops. The gateway now tracks the number of proxy hops using the `backstage-gateway-hops` header and rejects requests that exceed 3 hops with a 508 Loop Detected error. +- a17d9df: Update usage of the `instanceMetadata` service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-notifications-backend@0.6.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- 15fb764: Show default settings for notifications even before receiving first notification. + + Previously, it was not possible for the users to see or modify their notification settings until they had received at + least one notification from specific origin or topic. + This update ensures that default settings are displayed from the outset, + allowing users to customize their preferences immediately. + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-notifications-common@0.2.0 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0 + +### Minor Changes + +- ff96d7e: fix scaffolder action createDeployToken to allow usage of oauth tokens + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-search@1.5.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-react@1.10.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-techdocs@1.16.0 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- a4d4a70: Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/app-defaults@1.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + +## @backstage/backend-defaults@0.13.1 + +### Patch Changes + +- 9bcfa77: Adjusted the log line wording of task worker starting + +- 91ab2eb: Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- b2f6a5a: Fix #31348 issue where BitbucketUrlReader ignored provided token and instead always used integration credentials + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/backend-dynamic-feature-service@0.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-app-node@0.1.39 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/catalog-client@1.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.6 + +## @backstage/catalog-model@1.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/cli@0.34.5 + +### Patch Changes + +- fc7cbfc: The templates executed with the `yarn new` command now supports templating filenames. +- da19cb5: Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. +- fc7cbfc: Added a template for the `yarn new` command to create an catalog entity provider. To add this template to an explicit list in the root `package.json`, use `@backstage/cli/templates/catalog-provider-module`. +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 279279c: Fixes an issue where using the `backstage-cli new --scope` command with a scope that already includes the `@` symbol (e.g., `@backstage-community`) would result in a double `@@` prefix in the generated package name, causing invalid `package.json` files. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/eslint-plugin@0.2.0 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## @backstage/cli-node@0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/config@1.3.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +## @backstage/config-loader@1.10.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/core-app-api@1.19.2 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @backstage/core-compat-api@0.5.4 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/core-components@0.18.3 + +### Patch Changes + +- 1c7e1ce: - Revert `viewbox` back to old values. + - Added `fullScreen` prop to `EntityCatalogGraphCard` +- 96ad674: Line numbers in LogViewer will not be selectable in UI anymore +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @backstage/create-app@0.7.6 + +### Patch Changes + +- 2c1fe37: Bumped create-app version. +- 20fae88: Bumped create-app version. +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. +- fc7cbfc: Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. + +## @backstage/dev-utils@1.1.17 + +### Patch Changes + +- b29a856: Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + +## @backstage/frontend-app-api@0.13.2 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/frontend-defaults@0.3.3 + +## @backstage/frontend-defaults@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-components@0.18.3 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + +## @backstage/frontend-dynamic-feature-loader@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/config@1.3.6 + +## @backstage/frontend-test-utils@0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + - @backstage/test-utils@1.7.13 + +## @backstage/integration@1.18.2 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/integration-aws-node@0.1.19 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/integration-react@1.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## @techdocs/cli@1.10.2 + +### Patch Changes + +- c2a2017: Fix for missing styles due to move to BUI. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/test-utils@1.7.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + +## @backstage/plugin-api-docs@0.13.1 + +### Patch Changes + +- 7c281a5: Add i18n support for Raw tab title and an error message +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-app@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/integration-react@1.2.12 + +## @backstage/plugin-app-backend@0.5.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-app-node@0.1.39 + +## @backstage/plugin-app-node@0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + +## @backstage/plugin-app-visualizer@0.1.25 + +### Patch Changes + +- e81b3f0: Improve tree visualizer to use a horizontal layout and fill the content space. +- 722e2df: Migrated to use `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-auth@0.1.2 + +### Patch Changes + +- 1609e79: Authentication content screen now uses application title. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + +## @backstage/plugin-auth-backend@0.25.6 + +### Patch Changes + +- a9315d0: Change internal `state` column to `text` to support state of over 255 characters + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- 51ff7d8: Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. + + Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-auth-react@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-bitbucket-cloud-common@0.3.4 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + +- Updated dependencies + - @backstage/integration@1.18.2 + +## @backstage/plugin-catalog-backend-module-aws@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-azure@0.3.11 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/backend-openapi-utils@0.6.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github@0.11.2 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of repositories. Please see the [GitHub Discovery documentation](https://backstage.io/docs/integrations/github/discovery#configuration) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.16 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. Please see the [GitHub Org Data documentation](https://backstage.io/docs/integrations/github/org#configuration-details) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-catalog-backend-module-github@0.11.2 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.5 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.6 + +### Patch Changes + +- 70745c5: Correctly handle entity removal computation when DB count query returns string +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-logs@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.2 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.16 + +### Patch Changes + +- a5bcb2a: fix wrong dereferencing for AsyncApi 3 documents +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + +## @backstage/plugin-catalog-common@1.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-catalog-graph@0.5.3 + +### Patch Changes + +- a2d7ae7: Ensure the catalog graph entity card respects the height prop so the visualization scales down properly on wide screens. +- 1c7e1ce: - Revert `viewbox` back to old values. + - Added `fullScreen` prop to `EntityCatalogGraphCard` +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-catalog-import@0.13.7 + +### Patch Changes + +- f987bfd: Fixed missing `catalog.entity.create` permission authorization in the default page extension. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-react@1.21.3 + +### Patch Changes + +- 36d7582: Added missing i18n +- 2b7924b: Apply default ordering of templates +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- 904d136: Fixed catalog filter "all" not appearing as selected when set as the initially selected filter. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/frontend-test-utils@0.4.1 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.23 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-config-schema@0.1.74 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-devtools@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-react@0.4.38 + +## @backstage/plugin-devtools-backend@0.5.11 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-devtools-common@0.1.19 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-events-backend@0.5.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-azure@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-gerrit@0.2.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-backend-module-github@0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-gitlab@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-kafka@0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-test-utils@0.1.50 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.17 + +## @backstage/plugin-events-node@0.4.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-home@0.8.14 + +### Patch Changes + +- 2ac5d29: Allow customization of `VisitList` by adding optional `enrichVisit`, `transformPathname`, `canSave` functions to `VisitsStorageApi`, along with `VisitDisplayProvider` for colors, labels +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-home-react@0.1.32 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-home-react@0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-kubernetes@0.12.13 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-react@0.5.13 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-backend@0.20.4 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-node@0.3.6 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-kubernetes-cluster@0.0.31 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-kubernetes-react@0.5.13 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-common@0.9.8 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-node@0.3.6 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-kubernetes-react@0.5.13 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-mcp-actions-backend@0.1.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-mui-to-bui@0.2.1 + +### Patch Changes + +- 5c614ff: Updated BUI checkbox preview example to align with new component API. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-notifications@0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-notifications-backend-module-email@0.3.16 + +### Patch Changes + +- 22a5362: Updated `AWS SES` client to version 2 to support `nodemailer` version 7. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-notifications-backend-module-slack@0.2.1 + +### Patch Changes + +- d959bec: When an error message is logged due to inability to send a message with the Slack SDK, include the Slack Channel ID in the message to aid debugging. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-notifications-node@0.2.21 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-org@0.6.46 + +### Patch Changes + +- 6db9e7e: Improved responsiveness of GroupProfileCard component +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-org-react@0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-permission-backend@0.7.6 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/plugin-permission-node@0.10.6 + +## @backstage/plugin-permission-common@0.9.3 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/config@1.3.6 + +## @backstage/plugin-permission-node@0.10.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-permission-react@0.4.38 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + +## @backstage/plugin-proxy-backend@0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-proxy-node@0.1.10 + +## @backstage/plugin-proxy-node@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @backstage/plugin-scaffolder@1.34.3 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/catalog-client@1.12.1 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-scaffolder-backend@3.0.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16 + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.15 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@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-common@1.7.3 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.3.4 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-notifications-node@0.2.21 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.15 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.5 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-common@1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-scaffolder-node@0.12.1 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.5 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-test-utils@1.10.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-scaffolder-node@0.12.1 + +## @backstage/plugin-scaffolder-react@1.19.3 + +### Patch Changes + +- 886a8a1: Fixed a bug in the Scaffolder's template parsing in the `useTemplateSchema` hook by removing the title instead of setting it to `undefined` +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-scaffolder-common@1.7.3 + +## @backstage/plugin-search-backend@2.0.8 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.8 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.50 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-node@1.3.17 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-common@1.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-signals@0.0.25 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- f0f006e: Fixes a bug where the `SignalClient` would try to subscribe to the same channel twice after an error, instead of just once. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-signals-backend@0.3.10 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/config@1.3.6 + - @backstage/plugin-signals-node@0.1.26 + +## @backstage/plugin-signals-node@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## @backstage/plugin-signals-react@0.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + +## @backstage/plugin-techdocs-backend@2.1.2 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-node@1.20.0 + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/catalog-client@1.12.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + +### Patch Changes + +- 6929480: ExpandableCollapse Techdocs Addon was breaking native sidebar collapse on Firefox +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/integration@1.18.2 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/integration-react@1.2.12 + +## @backstage/plugin-techdocs-node@1.13.9 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-techdocs-react@1.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-user-settings@0.8.29 + +### Patch Changes + +- 2b6fda3: Revert `storageApiRef` implementation +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-signals-react@0.0.17 + +## @backstage/plugin-user-settings-backend@0.3.8 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/plugin-signals-node@0.1.26 + +## example-app@0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-app-api@0.13.2 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mui-to-bui@0.2.1 + - @backstage/plugin-devtools@0.1.33 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## example-app-next@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-app-visualizer@0.1.25 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-auth@0.1.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/frontend-defaults@0.3.3 + - @backstage/plugin-app@0.3.2 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## app-next-example-plugin@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + +## example-backend@0.0.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.8 + - @backstage/plugin-kubernetes-backend@0.20.4 + - @backstage/plugin-techdocs-backend@2.1.2 + - @backstage/plugin-signals-backend@0.3.10 + - @backstage/plugin-notifications-backend@0.6.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.16 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-backend@0.7.6 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend@2.0.8 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-scaffolder-backend@3.0.1 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8 + - @backstage/plugin-devtools-backend@0.5.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mcp-actions-backend@0.1.5 + - @backstage/plugin-app-backend@0.5.8 + - @backstage/plugin-auth-backend-module-github-provider@0.3.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.14 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.6 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-proxy-backend@0.6.8 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16 + - @backstage/plugin-search-backend-module-catalog@0.3.10 + - @backstage/plugin-search-backend-module-explore@0.3.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.8 + +## e2e-test@0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.6 + +## @internal/frontend@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + +## @internal/scaffolder@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-scaffolder-react@1.19.3 + +## techdocs-cli-embedded-app@0.2.114 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + +## @internal/plugin-todo-list@1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## @internal/plugin-todo-list-backend@1.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## @internal/plugin-todo-list-common@1.0.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 diff --git a/docs/releases/v1.45.0-next.1-changelog.md b/docs/releases/v1.45.0-next.1-changelog.md new file mode 100644 index 0000000000..098dd21e02 --- /dev/null +++ b/docs/releases/v1.45.0-next.1-changelog.md @@ -0,0 +1,9366 @@ +# Release v1.45.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.45.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.45.0-next.1) + +## @techdocs/cli@1.10.2-next.0 + +# @techdocs/cli + +## 1.10.1-next.0 + +### Patch Changes + +- c2a2017: Fix for missing styles due to move to BUI. +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.0 + - @backstage/plugin-techdocs-node@1.13.9-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/cli-common@0.1.15 + +## 1.10.0 + +### Minor Changes + +- 43afbe5: Techdocs CLI serve supports automatic refresh, relying on `mkdocs` `watch` feature. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0 + - @backstage/config@1.3.5 + - @backstage/plugin-techdocs-node@1.13.8 + +## 1.9.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-techdocs-node@1.13.8-next.1 + +## 1.9.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-techdocs-node@1.13.8-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + +## 1.9.8 + +### Patch Changes + +- db63208: Fixed an issue where `@techdocs/cli serve` command did not pick up the latest changes to TechDocs. +- Updated dependencies + - @backstage/backend-defaults@0.12.1 + - @backstage/plugin-techdocs-node@1.13.7 + +## 1.9.8-next.0 + +### Patch Changes + +- db63208: Fixed an issue where `@techdocs/cli serve` command did not pick up the latest changes to TechDocs. +- Updated dependencies + - @backstage/backend-defaults@0.12.1-next.0 + - @backstage/plugin-techdocs-node@1.13.7-next.0 + +## 1.9.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.12.0 + - @backstage/plugin-techdocs-node@1.13.6 + +## 1.9.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + +## 1.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-techdocs-node@1.13.5 + +## 1.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + +## 1.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4 + +## 1.9.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 1.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 1.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + +## 1.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + +## 1.9.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + +## 1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + +## 1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.1 + - @backstage/plugin-techdocs-node@1.13.3-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.1-next.0 + - @backstage/plugin-techdocs-node@1.13.3-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0 + - @backstage/plugin-techdocs-node@1.13.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.2 + - @backstage/plugin-techdocs-node@1.13.2-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.1 + - @backstage/plugin-techdocs-node@1.13.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.9.0-next.0 + - @backstage/plugin-techdocs-node@1.13.2-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2 + - @backstage/plugin-techdocs-node@1.13.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.1-next.2 + +## 1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.1-next.1 + +## 1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.2-next.0 + - @backstage/plugin-techdocs-node@1.13.1-next.0 + +## 1.9.0 + +### Minor Changes + +- 8de3d2d: Allow configurable optional retries for publisher AWS S3 operations. + +### Patch Changes + +- 69f84ac: Internal update to work with dynamic imports. +- Updated dependencies + - @backstage/backend-defaults@0.8.0 + - @backstage/plugin-techdocs-node@1.13.0 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.3 + - @backstage/plugin-techdocs-node@1.13.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.0-next.1 + +## 1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.0-next.0 + +## 1.9.0-next.0 + +### Minor Changes + +- 8de3d2d: Allow configurable optional retries for publisher AWS S3 operations. + +### Patch Changes + +- 69f84ac: Internal update to work with dynamic imports. +- Updated dependencies + - @backstage/backend-defaults@0.8.0-next.0 + - @backstage/plugin-techdocs-node@1.13.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.8.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.12.16 + +## 1.8.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0-next.1 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/plugin-techdocs-node@1.12.16-next.1 + - @backstage/cli-common@0.1.15 + +## 1.8.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.7.0-next.0 + - @backstage/catalog-model@1.7.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1 + - @backstage/plugin-techdocs-node@1.12.16-next.0 + +## 1.8.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0 + - @backstage/plugin-techdocs-node@1.12.15 + - @backstage/catalog-model@1.7.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1 + +## 1.8.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.2 + - @backstage/plugin-techdocs-node@1.12.15-next.2 + - @backstage/catalog-model@1.7.2-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1-next.0 + +## 1.8.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.0 + - @backstage/plugin-techdocs-node@1.12.15-next.1 + +## 1.8.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.6.0-next.0 + - @backstage/plugin-techdocs-node@1.12.15-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.0 + +## 1.8.22 + +### Patch Changes + +- 702f41d: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/backend-defaults@0.5.3 + - @backstage/cli-common@0.1.15 + - @backstage/catalog-model@1.7.1 + - @backstage/plugin-techdocs-node@1.12.13 + +## 1.8.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.3 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.15-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.13-next.3 + +## 1.8.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.15-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.13-next.2 + +## 1.8.22-next.1 + +### Patch Changes + +- 702f41d: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.15-next.0 + - @backstage/backend-defaults@0.5.3-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.13-next.1 + +## 1.8.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.3-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.13-next.0 + +## 1.8.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1 + - @backstage/plugin-techdocs-node@1.12.12 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.2 + - @backstage/plugin-techdocs-node@1.12.12-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.12-next.1 + +## 1.8.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.1-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.12-next.0 + +## 1.8.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0 + - @backstage/plugin-techdocs-node@1.12.11 + - @backstage/catalog-model@1.7.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-techdocs-node@1.12.11-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## 1.8.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.11-next.0 + - @backstage/backend-defaults@0.5.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/plugin-techdocs-node@1.12.9-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/plugin-techdocs-node@1.12.9-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.9-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.9-next.0 + +## 1.8.16 + +### Patch Changes + +- c964a3d: Import discovery from backend-defaults instead of backend-common +- Updated dependencies + - @backstage/backend-defaults@0.4.0 + - @backstage/plugin-techdocs-node@1.12.8 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.8-next.1 + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.15-next.0 + +### Patch Changes + +- c964a3d: Import discovery from backend-defaults instead of backend-common +- Updated dependencies + - @backstage/backend-defaults@0.3.3-next.0 + - @backstage/plugin-techdocs-node@1.12.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.12 + +### Patch Changes + +- 2110d76: Removed `dockerode` dependency. +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/plugin-techdocs-node@1.12.5 + - @backstage/cli-common@0.1.14 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 1.8.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.5-next.3 + - @backstage/cli-common@0.1.14-next.0 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 1.8.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.5-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## 1.8.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-techdocs-node@1.12.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.11 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-techdocs-node@1.12.4 + +## 1.8.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + +## 1.8.10-next.0 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/plugin-techdocs-node@1.12.3 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/plugin-techdocs-node@1.12.3-next.1 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.3-next.0 + - @backstage/backend-common@0.21.7-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.8 + +### Patch Changes + +- 8caf2f7: Fix how the cli server mocks the new auth cookie endpoint. +- Updated dependencies + - @backstage/backend-common@0.21.6 + - @backstage/plugin-techdocs-node@1.12.2 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-techdocs-node@1.12.1 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## 1.8.6 + +### Patch Changes + +- 1bd4596: Removed the `ts-node` dev dependency. +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.0 + - @backstage/catalog-model@1.4.5 + - @backstage/cli-common@0.1.13 + +## 1.8.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.0-next.2 + - @backstage/backend-common@0.21.4-next.2 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0-next.1 + +## 1.8.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/plugin-techdocs-node@1.11.6-next.1 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/cli-common@0.1.13 + +## 1.8.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-techdocs-node@1.11.5-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/cli-common@0.1.13 + +## 1.8.2 + +### Patch Changes + +- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. + Updated dependency `@types/fs-extra` to `^11.0.0`. +- d2e3ab9: Updated dependency `dockerode` to `^4.0.0`. +- 6ba64c4: Updated dependency `commander` to `^12.0.0`. +- d8d243c: fix: mkdocs parameter casing +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/catalog-model@1.4.4 + - @backstage/plugin-techdocs-node@1.11.2 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-techdocs-node@1.11.2-next.3 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/plugin-techdocs-node@1.11.2-next.2 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/cli-common@0.1.13 + +## 1.8.2-next.1 + +### Patch Changes + +- d8d243c: fix: mkdocs parameter casing +- Updated dependencies + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/plugin-techdocs-node@1.11.2-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-techdocs-node@1.11.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-techdocs-node@1.11.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-techdocs-node@1.11.1-next.2 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + +## 1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.0 + +## 1.8.0 + +### Minor Changes + +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. +- b2dccad: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-techdocs-node@1.11.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.3 + +## 1.8.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.2 + +## 1.8.0-next.1 + +### Minor Changes + +- b2dccad7b3: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.11.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.0-next.0 + +### Minor Changes + +- d15d483a49: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-techdocs-node@1.11.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.7.0 + +### Minor Changes + +- 8600b86820: validate Docker status before running mkdocs server + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.10.0 + - @backstage/backend-common@0.19.9 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.6.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-techdocs-node@1.10.0-next.2 + +## 1.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.10.0-next.1 + - @backstage/backend-common@0.19.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.9.1-next.0 + +## 1.6.0 + +### Minor Changes + +- d06b30b050: Add possibility to use a mkdocs config file with a different name than `mkdocs. with the serve command using the `--mkdocs-config-file-name\` argument + +### Patch Changes + +- de42eebaaf: Bumped dev dependencies `@types/node` and `mock-fs`. +- 2b6e572051: Restructured tests. +- Updated dependencies + - @backstage/backend-common@0.19.8 + - @backstage/plugin-techdocs-node@1.9.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.6.0-next.2 + +### Minor Changes + +- d06b30b050: Add possibility to use a mkdocs config file with a different name than `mkdocs. with the serve command using the `--mkdocs-config-file-name\` argument + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-techdocs-node@1.9.0-next.2 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/cli-common@0.1.13-next.0 + - @backstage/config@1.1.1-next.0 + +## 1.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.7-next.1 + - @backstage/plugin-techdocs-node@1.8.2-next.1 + - @backstage/config@1.1.0 + - @backstage/catalog-model@1.4.2 + - @backstage/cli-common@0.1.13-next.0 + +## 1.5.2-next.0 + +### Patch Changes + +- de42eebaaf: Bumped dev dependencies `@types/node` and `mock-fs`. +- 2b6e572051: Restructured tests. +- Updated dependencies + - @backstage/cli-common@0.1.13-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/plugin-techdocs-node@1.8.2-next.0 + - @backstage/config@1.1.0 + - @backstage/catalog-model@1.4.2 + +## 1.5.0 + +### Minor Changes + +- 10a86bd4ae12: Add optional config and cli option for techdocs to specify default mkdocs plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.5 + - @backstage/config@1.1.0 + - @backstage/catalog-model@1.4.2 + - @backstage/plugin-techdocs-node@1.8.0 + - @backstage/cli-common@0.1.12 + +## 1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/config@1.1.0-next.2 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/cli-common@0.1.12 + - @backstage/plugin-techdocs-node@1.8.0-next.3 + +## 1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/plugin-techdocs-node@1.8.0-next.2 + - @backstage/cli-common@0.1.12 + +## 1.5.0-next.1 + +### Minor Changes + +- 10a86bd4ae12: Add optional config and cli option for techdocs to specify default mkdocs plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/plugin-techdocs-node@1.8.0-next.1 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/cli-common@0.1.12 + +## 1.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.4-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/plugin-techdocs-node@1.7.6-next.0 + +## 1.4.5 + +### Patch Changes + +- 971bdd6a4732: Bumped internal `nodemon` dependency. +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/plugin-techdocs-node@1.7.4 + - @backstage/catalog-model@1.4.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + +## 1.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-techdocs-node@1.7.4-next.2 + +## 1.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/plugin-techdocs-node@1.7.4-next.1 + - @backstage/catalog-model@1.4.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + +## 1.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/plugin-techdocs-node@1.7.4-next.0 + +## 1.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.1 + - @backstage/catalog-model@1.4.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/plugin-techdocs-node@1.7.3 + +## 1.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.1-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/plugin-techdocs-node@1.7.3-next.0 + +## 1.4.3 + +### Patch Changes + +- 68a21956ef52: Remove reference to deprecated import +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/plugin-techdocs-node@1.7.2 + - @backstage/catalog-model@1.4.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + +## 1.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/plugin-techdocs-node@1.7.2-next.2 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/plugin-techdocs-node@1.7.2-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/catalog-model@1.3.0 + - @backstage/cli-common@0.1.12 + - @backstage/plugin-techdocs-node@1.7.2-next.0 + +## 1.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/plugin-techdocs-node@1.7.1 + - @backstage/catalog-model@1.3.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/plugin-techdocs-node@1.7.1-next.1 + - @backstage/config@1.0.7 + +## 1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/plugin-techdocs-node@1.7.1-next.0 + - @backstage/catalog-model@1.3.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.1 + +### Patch Changes + +- b348420a804: Adding global-agent to enable the ability to publish through a proxy +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/plugin-techdocs-node@1.7.0 + - @backstage/catalog-model@1.3.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/backend-common@0.18.4-next.2 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/plugin-techdocs-node@1.6.1-next.3 + +## 1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/catalog-model@1.2.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/plugin-techdocs-node@1.6.1-next.2 + +## 1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.4-next.1 + - @backstage/catalog-model@1.2.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/plugin-techdocs-node@1.6.1-next.1 + +## 1.4.1-next.0 + +### Patch Changes + +- b348420a804: Adding global-agent to enable the ability to publish through a proxy +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/catalog-model@1.2.1 + - @backstage/cli-common@0.1.12 + - @backstage/plugin-techdocs-node@1.6.1-next.0 + +## 1.4.0 + +### Minor Changes + +- 8e465ce52e2: Running `@techdocs/cli generate` with the `--verbose` flag will now print the mkdocs output. +- ea2bbef1b16: Added support for an HTTPS proxy for techdocs AWS S3 requests + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.6.0 + - @backstage/backend-common@0.18.3 + - @backstage/catalog-model@1.2.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + +## 1.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.6.0-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/config@1.0.7-next.0 + +## 1.4.0-next.1 + +### Minor Changes + +- 8e465ce52e2: Running `@techdocs/cli generate` with the `--verbose` flag will now print the mkdocs output. +- ea2bbef1b16: Added support for an HTTPS proxy for techdocs AWS S3 requests + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.6.0-next.1 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/cli-common@0.1.12-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/catalog-model@1.2.1-next.1 + +## 1.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.3-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/plugin-techdocs-node@1.5.1-next.0 + +## 1.3.2 + +### Patch Changes + +- dd1e37649f: Deprecated getMkDocsYml in favor of getMkdocsYml (lowercase 'd') + +- dcacf94912: Fix proxying to mkdocs + + The domain localhost may point to both 127.0.0.1 and ::1, ipv4 and ipv6 + and when node tries to lookup localhost it might prefer ipv6 while mkdocs + is only listening on ipv4. This tells node-proxy to target the ipv4 address + instead of relying on localhost hostname lookup. + +- 339d9a5b5c: Added support for using a default `mkdocs.yml` configuration file when none is provided + +- 6e0b6a0d50: Fixed publish command missing awsBucketRootPath option. + Fixed publish command having the gcsBucketRootPath option misconfigured, previously returning a boolean vs a string. + +- Updated dependencies + - @backstage/backend-common@0.18.2 + - @backstage/plugin-techdocs-node@1.5.0 + - @backstage/catalog-model@1.2.0 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.2 + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/plugin-techdocs-node@1.4.6-next.2 + +## 1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/plugin-techdocs-node@1.4.6-next.1 + +## 1.3.2-next.0 + +### Patch Changes + +- 6e0b6a0d50: Fixed publish command missing awsBucketRootPath option. + Fixed publish command having the gcsBucketRootPath option misconfigured, previously returning a boolean vs a string. +- Updated dependencies + - @backstage/plugin-techdocs-node@1.4.6-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/backend-common@0.18.2-next.0 + +## 1.3.0 + +### Minor Changes + +- bc18c902a2: Add `--preview-app-bundle-path` and `--preview-app-port` options to the `serve` command enabling previewing with apps other than the provided one + +### Patch Changes + +- 9f2b786fc9: Provide context for logged errors. +- Updated dependencies + - @backstage/backend-common@0.18.0 + - @backstage/catalog-model@1.1.5 + - @backstage/config@1.0.6 + - @backstage/cli-common@0.1.11 + - @backstage/plugin-techdocs-node@1.4.4 + +## 1.3.0-next.2 + +### Minor Changes + +- bc18c902a2: Add `--preview-app-bundle-path` and `--preview-app-port` options to the `serve` command enabling previewing with apps other than the provided one + +### Patch Changes + +- 9f2b786fc9: Provide context for logged errors. +- Updated dependencies + - @backstage/backend-common@0.18.0-next.1 + - @backstage/plugin-techdocs-node@1.4.4-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6-next.0 + +## 1.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.0-next.0 + - @backstage/config@1.0.6-next.0 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/cli-common@0.1.11 + - @backstage/plugin-techdocs-node@1.4.4-next.1 + +## 1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.5-next.0 + - @backstage/backend-common@0.17.0 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.5 + - @backstage/plugin-techdocs-node@1.4.4-next.0 + +## 1.2.4 + +### Patch Changes + +- 8015ff1258: Tweaked wording to use inclusive terminology +- Updated dependencies + - @backstage/backend-common@0.17.0 + - @backstage/plugin-techdocs-node@1.4.3 + - @backstage/cli-common@0.1.11 + - @backstage/catalog-model@1.1.4 + - @backstage/config@1.0.5 + +## 1.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.3 + - @backstage/plugin-techdocs-node@1.4.3-next.3 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/cli-common@0.1.11-next.0 + - @backstage/config@1.0.5-next.1 + +## 1.2.4-next.2 + +### Patch Changes + +- 8015ff1258: Tweaked wording to use inclusive terminology +- Updated dependencies + - @backstage/backend-common@0.17.0-next.2 + - @backstage/cli-common@0.1.11-next.0 + - @backstage/plugin-techdocs-node@1.4.3-next.2 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + +## 1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/plugin-techdocs-node@1.4.3-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/cli-common@0.1.10 + +## 1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.1-next.0 + - @backstage/catalog-model@1.1.4-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.5-next.0 + - @backstage/plugin-techdocs-node@1.4.3-next.0 + +## 1.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/plugin-techdocs-node@1.4.2 + - @backstage/catalog-model@1.1.3 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4 + +## 1.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/plugin-techdocs-node@1.4.2-next.1 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + +## 1.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + +## 1.2.2 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + +## 1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + +## 1.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-techdocs-node@1.4.1-next.1 + +## 1.2.2-next.0 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-techdocs-node@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + +## 1.2.1 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/plugin-techdocs-node@1.4.0 + - @backstage/catalog-model@1.1.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.2 + +## 1.2.1-next.2 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/catalog-model@1.1.1-next.0 + - @backstage/cli-common@0.1.10-next.0 + - @backstage/config@1.0.2-next.0 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/plugin-techdocs-node@1.4.0-next.2 + +## 1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.4.0-next.1 + - @backstage/backend-common@0.15.1-next.2 + +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/plugin-techdocs-node@1.3.1-next.0 + +## 1.2.0 + +### Minor Changes + +- 855952db53: Added CLI option `--docker-option` to allow passing additional options to the `docker run` command executed my `serve` and `serve:mkdocs`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/plugin-techdocs-node@1.3.0 + +## 1.2.0-next.2 + +### Minor Changes + +- 855952db53: Added CLI option `--docker-option` to allow passing additional options to the `docker run` command executed my `serve` and `serve:mkdocs`. + +## 1.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.1 + - @backstage/plugin-techdocs-node@1.3.0-next.1 + +## 1.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/plugin-techdocs-node@1.2.1-next.0 + +## 1.1.3 + +### Patch Changes + +- a5d73da942: Fix the flag parsing for `legacyCopyReadmeMdToIndexMd` in `techdocs-cli generate` command, and decouple it's logic from the `techdocs-ref` flag. +- 14ce0d9347: Fixed a bug that prevented docker images from being pulled by default when generating TechDocs. +- Updated dependencies + - @backstage/plugin-techdocs-node@1.2.0 + - @backstage/backend-common@0.14.1 + - @backstage/catalog-model@1.1.0 + +## 1.1.3-next.1 + +### Patch Changes + +- a5d73da942: Fix the flag parsing for `legacyCopyReadmeMdToIndexMd` in `techdocs-cli generate` command, and decouple it's logic from the `techdocs-ref` flag. +- Updated dependencies + - @backstage/plugin-techdocs-node@1.2.0-next.1 + - @backstage/catalog-model@1.1.0-next.1 + - @backstage/backend-common@0.14.1-next.1 + +## 1.1.3-next.0 + +### Patch Changes + +- 14ce0d9347: Fixed a bug that prevented docker images from being pulled by default when generating TechDocs. +- Updated dependencies + - @backstage/backend-common@0.14.1-next.0 + - @backstage/catalog-model@1.1.0-next.0 + - @backstage/plugin-techdocs-node@1.1.3-next.0 + +## 1.1.2 + +### Patch Changes + +- f96e98f4cd: Updated dependency `cypress` to `^10.0.0`. +- bff65e6958: Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()` from `@backstage/core-components`. +- Updated dependencies + - @backstage/backend-common@0.14.0 + - @backstage/plugin-techdocs-node@1.1.2 + - @backstage/catalog-model@1.0.3 + +## 1.1.2-next.2 + +### Patch Changes + +- f96e98f4cd: Updated dependency `cypress` to `^10.0.0`. +- Updated dependencies + - @backstage/backend-common@0.14.0-next.2 + - @backstage/plugin-techdocs-node@1.1.2-next.2 + +## 1.1.2-next.1 + +### Patch Changes + +- bff65e6958: Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()` from `@backstage/core-components`. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.1 + - @backstage/catalog-model@1.0.3-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.1 + +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.0 + +## 1.1.1 + +### Patch Changes + +- 344ea56acc: Bump `commander` to version 9.1.0 +- 52fddad92d: The TechDocs CLI's embedded app now imports all API refs from the `@backstage/plugin-techdocs-react` package. +- c14e78a367: Update `techdocs-cli serve`'s `proxyEndpoint` to match the base URL of the embedded techdocs app. +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/cli-common@0.1.9 + - @backstage/config@1.0.1 + - @backstage/plugin-techdocs-node@1.1.1 + - @backstage/catalog-model@1.0.2 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/cli-common@0.1.9-next.0 + - @backstage/config@1.0.1-next.0 + - @backstage/catalog-model@1.0.2-next.0 + - @backstage/plugin-techdocs-node@1.1.1-next.1 + +## 1.1.1-next.1 + +### Patch Changes + +- 52fddad92d: The TechDocs CLI's embedded app now imports all API refs from the `@backstage/plugin-techdocs-react` package. +- Updated dependencies + - @backstage/backend-common@0.13.3-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- 344ea56acc: Bump `commander` to version 9.1.0 +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/plugin-techdocs-node@1.1.1-next.0 + +## 1.1.0 + +### Minor Changes + +- 733187987b: Removed an undocumented, broken behavior where `README.md` files would be copied to `index.md` if it did not exist, leading to broken links in the TechDocs UI. + + **WARNING**: If you notice 404s in TechDocs after updating, check to make sure that all markdown files referenced in your `mkdocs.yml`s' `nav` sections exist. The following flag may be passed to the `generate` command to temporarily revert to the broken behavior. + + ```sh + techdocs-cli generate --legacyCopyReadmeMdToIndexMd + ``` + +### Patch Changes + +- 230ad0826f: Bump to using `@types/node` v16 +- eb470ea54c: Adds a new flag to override the entrypoint when using a custom docker image. It could be used to reuse existing images with different entrypoints. +- Updated dependencies + - @backstage/catalog-model@1.0.1 + - @backstage/backend-common@0.13.2 + - @backstage/plugin-techdocs-node@1.1.0 + +## 1.1.0-next.1 + +### Minor Changes + +- bcf1a2496c: BREAKING: The default Techdocs behavior will no longer attempt to copy `docs/README.md` or `README.md` to `docs/index.md` (if not found). To retain this behavior in your instance, you can set the following config in your `app-config.yaml`: + + ```yaml + techdocs: + generator: + mkdocs: + legacyCopyReadmeMdToIndexMd: true + ``` + +### Patch Changes + +- 230ad0826f: Bump to using `@types/node` v16 +- eb470ea54c: Adds a new flag to override the entrypoint when using a custom docker image. It could be used to reuse existing images with different entrypoints. +- Updated dependencies + - @backstage/backend-common@0.13.2-next.2 + - @backstage/plugin-techdocs-node@1.1.0-next.2 + +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-techdocs-node@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-techdocs-node@1.0.0 + +## 0.8.17 + +### Patch Changes + +- e0a69ba49f: build(deps): bump `fs-extra` from 9.1.0 to 10.0.1 +- 91bf1e6c1a: Use `@backstage/plugin-techdocs-node` package instead of `@backstage/techdocs-common`. +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/plugin-techdocs-node@0.11.12 + - @backstage/catalog-model@0.13.0 + +## 0.8.17-next.0 + +### Patch Changes + +- e0a69ba49f: build(deps): bump `fs-extra` from 9.1.0 to 10.0.1 +- 91bf1e6c1a: Use `@backstage/plugin-techdocs-node` package instead of `@backstage/techdocs-common`. +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/plugin-techdocs-node@0.11.12-next.0 + - @backstage/catalog-model@0.13.0-next.0 + +## 0.8.16 + +### Patch Changes + +- 853efd42bd: Bump `@backstage/techdocs-common` to `0.11.10` to use `spotify/techdocs:v0.3.7` which upgrades `mkdocs-theme` as a dependency of `mkdocs-techdocs-core`. +- Updated dependencies + - @backstage/catalog-model@0.12.0 + - @backstage/backend-common@0.12.0 + - @backstage/techdocs-common@0.11.11 + +## 0.8.15 + +### Patch Changes + +- ed78516480: chore(deps-dev): bump `cypress` from 7.3.0 to 9.5.0 +- 209fd128e6: Updated usage of `github:` location types in docs to use `url:` instead. +- 61ff215e08: - Adds `cypress` and `cypress-plugin-snapshots` as dependencies for integration and visual regression tests. + - Updates README documentation with instructions for how to run tests. + - Clarifies output text for prepack script. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/techdocs-common@0.11.10 + +## 0.8.14 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-model@0.10.0 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/techdocs-common@0.11.8 + +## 0.8.13 + +### Patch Changes + +- b70c186194: Updated the HTTP server to allow for simplification of the development of the CLI itself. +- Updated dependencies + - @backstage/backend-common@0.10.7 + - @backstage/techdocs-common@0.11.7 + +## 0.8.13-next.0 + +### Patch Changes + +- b70c186194: Updated the HTTP server to allow for simplification of the development of the CLI itself. +- Updated dependencies + - @backstage/backend-common@0.10.7-next.0 + - @backstage/techdocs-common@0.11.7-next.0 + +## 0.8.12 + +### Patch Changes + +- 14472509a3: Use a local file dependency for techdocs-cli-embedded-app, to ensure that it's always pulled out of the workspace +- Updated dependencies + - @backstage/backend-common@0.10.6 + - @backstage/techdocs-common@0.11.6 + +## 0.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/techdocs-common@0.11.6-next.0 + +## 0.8.12-next.0 + +### Patch Changes + +- 14472509a3: Use a local file dependency for techdocs-cli-embedded-app, to ensure that it's always pulled out of the workspace + +## 0.8.11 + +### Patch Changes + +- 10086f5873: Bumped `react-dev-utils` from `^12.0.0-next.47` to `^12.0.0-next.60`. +- Updated dependencies + - @backstage/techdocs-common@0.11.5 + - @backstage/backend-common@0.10.5 + +## 0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.4-next.0 + - @backstage/config@0.1.13-next.0 + - @backstage/techdocs-common@0.11.4-next.0 + - @backstage/catalog-model@0.9.10-next.0 + +## 0.8.10 + +### Patch Changes + +- 8fbc988bfc: remove internal and inline CSS from index.html +- Updated dependencies + - @backstage/techdocs-common@0.11.2 + - @backstage/backend-common@0.10.1 + +## 0.8.9 + +### Patch Changes + +- 5fdc8df0e8: The `index.html` template was updated to use the new `config` global. +- Updated dependencies + - @backstage/backend-common@0.10.0 + - @backstage/techdocs-common@0.11.1 + +## 0.8.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.13 + - @backstage/techdocs-common@0.11.0 + +## 0.8.7 + +### Patch Changes + +- e7230ef814: Bump react-dev-utils to v12 +- Updated dependencies + - @backstage/backend-common@0.9.12 + +## 0.8.6 + +### Patch Changes + +- e21e3c6102: Bumping minimum requirements for `dockerode` and `testcontainers` +- 1578ad341b: Add support for specifying bucketRootPath for AWS and GCS publishers +- f2694e3750: Adds ability to use encrypted S3 buckets by utilizing the SSE option in the AWS SDK +- Updated dependencies + - @backstage/catalog-model@0.9.7 + - @backstage/backend-common@0.9.10 + - @backstage/techdocs-common@0.10.7 + +## 0.8.5 + +### Patch Changes + +- Reunified the [techdocs-cli](https://github.com/backstage/techdocs-cli) monorepo code back into the main [backstage](https://github.com/backstage/backstage) repo + + See [7288](https://github.com/backstage/backstage/issues/7288)). The changes include some internal refactoring that do not affect functionality beyond the local development setup. + +## 0.8.4 + +### Patch Changes + +- 8333394: The [change](https://github.com/backstage/techdocs-cli/commit/b25014cec313d46ce1c9b4f324cc09047a00fc1f) updated the `@backstage/techdocs-common` from version `0.9.0` to `0.10.2` and one of the intermediate versions, the [0.10.0](https://github.com/backstage/backstage/blob/cac4afb95fdbd130a66e53a1b0430a1e62787a7f/packages/techdocs-common/CHANGELOG.md#patch-changes-2), introduced the use of search in context that requires an implementation for the Search API. + + Created a custom techdocs page to disable search in the Reader component, preventing it from using the Search API, as we don't want to provide search in preview mode. + +## 0.8.3 + +### Patch Changes + +- edbb988: Upgrades the techdocs common page to the latest version 0.10.2. + + See [@backstage/techdocs-common changelog](https://github.com/backstage/backstage/blob/cac4afb95fdbd130a66e53a1b0430a1e62787a7f/packages/techdocs-common/CHANGELOG.md#L3). + +- db4ebfc: Add an `etag` flag to the `generate` command that is stored in the `techdocs_metadata.json` file. + +## 0.8.2 + +### Patch Changes + +- 8fc7384: Allow to execute techdocs-cli serve using docker techdocs-container on Windows + +## 0.8.1 + +### Patch Changes + +- 0187424: Separate build and publish release steps + +## 0.8.0 + +### Minor Changes + +- c6f437a: OpenStack Swift configuration changed due to OSS SDK Client change in @backstage/techdocs-common, it was a breaking change. + PR Reference: + +### Patch Changes + +- 05f0409: Merge Jobs for Release Pull Requests and Package Publishes + +## 0.7.0 + +### Minor Changes + +- 9d1f8d8: The `techdocs-cli publish` command will now publish TechDocs content to remote + storage using the lowercase'd entity triplet as the storage path. This is in + line with the beta release of the TechDocs plugin (`v0.11.0`). + + If you have been running `techdocs-cli` prior to this version, you will need to + follow this [migration guide](https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-alpha-to-beta). + +## 0.6.2 + +### Patch Changes + +- f1bcf1a: Changelog (from v0.6.1 to v0.6.2) + + #### :bug: Bug Fix + + - `techdocs-cli` + - [#105](https://github.com/backstage/techdocs-cli/pull/105) Add azureAccountKey parameter back to the publish command ([@emmaindal](https://github.com/emmaindal)) + + #### :house: Internal + + - `techdocs-cli-embedded-app` + - [#122](https://github.com/backstage/techdocs-cli/pull/122) chore(deps-dev): bump @types/node from 12.20.20 to 16.7.1 in /packages/techdocs-cli-embedded-app ([@dependabot\[bot\]](https://github.com/apps/dependabot)) + - [#120](https://github.com/backstage/techdocs-cli/pull/120) chore(deps-dev): bump @types/react-dom from 16.9.14 to 17.0.9 in /packages/techdocs-cli-embedded-app ([@dependabot\[bot\]](https://github.com/apps/dependabot)) + - [#119](https://github.com/backstage/techdocs-cli/pull/119) chore(deps-dev): bump @testing-library/user-event from 12.8.3 to 13.2.1 in /packages/techdocs-cli-embedded-app ([@dependabot\[bot\]](https://github.com/apps/dependabot)) + - [#118](https://github.com/backstage/techdocs-cli/pull/118) chore(deps-dev): bump @testing-library/react from 10.4.9 to 12.0.0 ([@dependabot\[bot\]](https://github.com/apps/dependabot)) + - Other + - [#117](https://github.com/backstage/techdocs-cli/pull/117) chore(deps): bump @backstage/plugin-catalog from 0.6.11 to 0.6.12 ([@dependabot\[bot\]](https://github.com/apps/dependabot)) + - [#124](https://github.com/backstage/techdocs-cli/pull/124) Update release process docs ([@emmaindal](https://github.com/emmaindal)) + - [#116](https://github.com/backstage/techdocs-cli/pull/116) ignore dependabot branches for project board workflow ([@emmaindal](https://github.com/emmaindal)) + - [#106](https://github.com/backstage/techdocs-cli/pull/106) Configure dependabot for all packages ([@emmaindal](https://github.com/emmaindal)) + - [#102](https://github.com/backstage/techdocs-cli/pull/102) readme: add information about running techdocs-common locally ([@vcapretz](https://github.com/vcapretz)) + - [#103](https://github.com/backstage/techdocs-cli/pull/103) Introduce changesets and improve the publish workflow ([@minkimcello](https://github.com/minkimcello)) + - [#101](https://github.com/backstage/techdocs-cli/pull/101) update yarn lockfile to get rid of old version of node-forge ([@emmaindal](https://github.com/emmaindal)) + + #### Committers: 3 + + Thank you for contributing ❤️ + + - `Emma Indal` ([@emmaindal](https://github.com/emmaindal)) + - `Min Kim` ([@minkimcello](https://github.com/minkimcello)) + - `Vitor Capretz` ([@vcapretz](https://github.com/vcapretz)) + +## @backstage/plugin-techdocs@1.15.3-next.0 + +# @backstage/plugin-techdocs + +## 1.15.2-next.0 + +### Patch Changes + +- a4d4a70: Fixed an issue where the entire TechDocs page would re-render when navigating between pages within the same entity's documentation. +- Updated dependencies + - @backstage/plugin-search-react@1.9.6-next.0 + - @backstage/plugin-catalog-react@1.21.3-next.0 + - @backstage/core-plugin-api@1.11.2-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/core-components@0.18.3-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/integration@1.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.2-next.0 + - @backstage/catalog-client@1.12.1-next.0 + - @backstage/core-compat-api@0.5.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.12-next.0 + - @backstage/theme@0.7.0 + - @backstage/plugin-auth-react@0.1.21-next.0 + - @backstage/plugin-search-common@1.2.21-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.5-next.0 + +## 1.15.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2 + - @backstage/core-components@0.18.2 + - @backstage/integration@1.18.1 + - @backstage/frontend-plugin-api@0.12.1 + - @backstage/plugin-search-react@1.9.5 + - @backstage/config@1.3.5 + - @backstage/theme@0.7.0 + - @backstage/core-compat-api@0.5.3 + - @backstage/plugin-techdocs-react@1.3.4 + - @backstage/core-plugin-api@1.11.1 + - @backstage/integration-react@1.2.11 + - @backstage/plugin-auth-react@0.1.20 + - @backstage/plugin-search-common@1.2.20 + +## 1.15.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + +## 1.15.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## 1.15.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.15.0 + +### Minor Changes + +- a0b604c: Adding redirect handling for TechDocs URLs that reference entities that now reference an external entity for TechDocs. Including tests and documentation. + +### Patch Changes + +- 313cec7: Updated dependency `dompurify` to `^3.2.4`. +- 8d18d23: TechDocs page titles have been improved, especially for deeply nested pages. +- 1dfee19: Reverts a change in CSS layout that shifted the content of the Techdocs too far to the left. +- 4ce5831: Support Techdocs redirect with dompurify 3.2.6+ +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.0 + - @backstage/plugin-techdocs-react@1.3.3 + - @backstage/frontend-plugin-api@0.12.0 + - @backstage/plugin-auth-react@0.1.19 + - @backstage/core-plugin-api@1.11.0 + - @backstage/catalog-client@1.12.0 + - @backstage/integration@1.18.0 + - @backstage/core-components@0.18.0 + - @backstage/core-compat-api@0.5.2 + - @backstage/plugin-search-react@1.9.4 + - @backstage/integration-react@1.2.10 + +## 1.14.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-react@0.1.19-next.1 + - @backstage/catalog-client@1.12.0-next.0 + - @backstage/plugin-catalog-react@1.21.0-next.2 + - @backstage/core-components@0.17.6-next.1 + - @backstage/integration@1.18.0-next.0 + - @backstage/core-compat-api@0.5.2-next.2 + +## 1.14.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.2-next.1 + - @backstage/frontend-plugin-api@0.11.1-next.0 + - @backstage/integration@1.18.0-next.0 + - @backstage/core-components@0.17.6-next.0 + - @backstage/core-compat-api@0.5.2-next.1 + - @backstage/plugin-search-react@1.9.4-next.0 + - @backstage/plugin-techdocs-react@1.3.3-next.0 + - @backstage/integration-react@1.2.10-next.0 + - @backstage/plugin-auth-react@0.1.19-next.0 + +## 1.14.2-next.0 + +### Patch Changes + +- 1dfee19: Reverts a change in CSS layout that shifted the content of the Techdocs too far to the left. +- Updated dependencies + - @backstage/core-compat-api@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.20.2-next.0 + - @backstage/catalog-client@1.11.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-components@0.17.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.11.0 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8 + - @backstage/plugin-auth-react@0.1.18 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.3 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2 + +## 1.14.0 + +### Minor Changes + +- cb0541f: Adds `additionalAllowedURIProtocols` to sanitizer config + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- f231c2b: Fixes CSS to adjust based on whether or not the global Backstage sidebar is on the page. +- Updated dependencies + - @backstage/core-components@0.17.5 + - @backstage/frontend-plugin-api@0.11.0 + - @backstage/core-compat-api@0.5.0 + - @backstage/plugin-search-react@1.9.3 + - @backstage/plugin-catalog-react@1.20.0 + - @backstage/theme@0.6.8 + - @backstage/catalog-client@1.11.0 + - @backstage/plugin-auth-react@0.1.18 + - @backstage/plugin-techdocs-react@1.3.2 + +## 1.14.0-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 1.14.0-next.1 + +### Minor Changes + +- cb0541f: Adds `additionalAllowedURIProtocols` to sanitizer config + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## 1.13.2 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/theme@0.6.7 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## 1.13.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + +## 1.13.2-next.0 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.0 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + +## 1.13.0-next.2 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## 1.13.0-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.13.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + +## 1.12.6 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 72d019d: Removed various typos +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## 1.12.6-next.2 + +### Patch Changes + +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.6-next.1 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/theme@0.6.6-next.0 + - @backstage/core-components@0.17.2-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.0 + - @backstage/core-compat-api@0.4.2-next.1 + - @backstage/integration@1.16.4-next.1 + - @backstage/plugin-search-react@1.9.0-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.0 + - @backstage/plugin-auth-react@0.1.15-next.0 + - @backstage/integration-react@1.2.7-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.16.4-next.0 + - @backstage/core-compat-api@0.4.2-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.0 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/integration-react@1.2.7-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.1 + - @backstage/core-plugin-api@1.10.6 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.1 + - @backstage/theme@0.6.5 + - @backstage/plugin-auth-react@0.1.14 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.8 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.16 + +## 1.12.5 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.16 + - @backstage/plugin-catalog-react@1.17.0 + - @backstage/frontend-plugin-api@0.10.1 + - @backstage/integration-react@1.2.6 + - @backstage/core-compat-api@0.4.1 + - @backstage/core-components@0.17.1 + - @backstage/core-plugin-api@1.10.6 + - @backstage/plugin-search-react@1.8.8 + - @backstage/plugin-auth-react@0.1.14 + - @backstage/theme@0.6.5 + - @backstage/integration@1.16.3 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.5-next.2 + +### Patch Changes + +- a47fd39: Removes instances of default React imports, a necessary update for the upcoming React 19 migration. + + + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.1-next.1 + - @backstage/integration-react@1.2.6-next.1 + - @backstage/core-compat-api@0.4.1-next.2 + - @backstage/core-components@0.17.1-next.1 + - @backstage/core-plugin-api@1.10.6-next.0 + - @backstage/plugin-techdocs-react@1.2.16-next.1 + - @backstage/plugin-catalog-react@1.17.0-next.2 + - @backstage/plugin-search-react@1.8.8-next.1 + - @backstage/plugin-auth-react@0.1.14-next.1 + - @backstage/theme@0.6.5-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.16.3-next.0 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.16-next.0 + - @backstage/core-components@0.17.1-next.0 + - @backstage/integration-react@1.2.6-next.0 + - @backstage/integration@1.16.3-next.0 + - @backstage/frontend-plugin-api@0.10.1-next.0 + - @backstage/plugin-auth-react@0.1.14-next.0 + - @backstage/plugin-catalog-react@1.16.1-next.1 + - @backstage/plugin-search-react@1.8.8-next.0 + - @backstage/core-compat-api@0.4.1-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.5 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.4 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.16.1-next.0 + - @backstage/core-compat-api@0.4.1-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.0 + - @backstage/core-plugin-api@1.10.5 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.0 + - @backstage/integration@1.16.2 + - @backstage/integration-react@1.2.5 + - @backstage/theme@0.6.4 + - @backstage/plugin-auth-react@0.1.13 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.7 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.15 + +## 1.12.4 + +### Patch Changes + +- fffe3c0: Fixed double scrollbar issue that would appear on the Entity TechDocs view page that would stop the page from full scrolling to the top when navigating to a new page +- 065e6b9: Excludes SVG styling from sanitization +- b5a8208: Added `TechDocsAddonsBlueprint` extension to allow adding of techdocs addons. +- ed1cb3e: Adds the page name of techdocs to the document's title. +- fe4abb8: Updates logic to check for SVG sources when inlining them. +- Updated dependencies + - @backstage/core-components@0.17.0 + - @backstage/core-plugin-api@1.10.5 + - @backstage/integration@1.16.2 + - @backstage/plugin-search-react@1.8.7 + - @backstage/frontend-plugin-api@0.10.0 + - @backstage/plugin-catalog-react@1.16.0 + - @backstage/core-compat-api@0.4.0 + - @backstage/plugin-techdocs-react@1.2.15 + - @backstage/integration-react@1.2.5 + - @backstage/plugin-auth-react@0.1.13 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.4 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.0-next.2 + - @backstage/plugin-catalog-react@1.16.0-next.2 + - @backstage/core-compat-api@0.4.0-next.2 + - @backstage/core-components@0.16.5-next.1 + - @backstage/integration@1.16.2-next.0 + - @backstage/plugin-search-react@1.8.7-next.2 + - @backstage/plugin-techdocs-react@1.2.15-next.2 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.4 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.5-next.0 + - @backstage/theme@0.6.4 + - @backstage/plugin-auth-react@0.1.13-next.1 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.4-next.1 + +### Patch Changes + +- fffe3c0: Fixed double scrollbar issue that would appear on the Entity TechDocs view page that would stop the page from full scrolling to the top when navigating to a new page +- Updated dependencies + - @backstage/core-components@0.16.5-next.0 + - @backstage/plugin-catalog-react@1.16.0-next.1 + - @backstage/core-compat-api@0.3.7-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.4 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.9.6-next.1 + - @backstage/integration@1.16.1 + - @backstage/integration-react@1.2.4 + - @backstage/theme@0.6.4 + - @backstage/plugin-auth-react@0.1.13-next.0 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.7-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.15-next.1 + +## 1.12.4-next.0 + +### Patch Changes + +- 065e6b9: Excludes svg styling from sanitization +- b5a8208: Added `TechDocsAddonsBlueprint` extension to allow adding of techdocs addons. +- ed1cb3e: Adds the page name of techdoc to the document's title. +- fe4abb8: Updates logic to check for SVG sources when inlining svgs. +- Updated dependencies + - @backstage/plugin-search-react@1.8.7-next.0 + - @backstage/plugin-catalog-react@1.16.0-next.0 + - @backstage/plugin-techdocs-react@1.2.15-next.0 + - @backstage/frontend-plugin-api@0.9.6-next.0 + - @backstage/core-compat-api@0.3.7-next.0 + - @backstage/integration-react@1.2.4 + +## 1.12.3 + +### Patch Changes + +- eb3d91a: Use the custom error page if provided for displaying errors instead of the default error page + +- 524f0af: Add missing route ref to the `/alpha` entity content extension. + +- f4be934: Changed the base URL in addLinkClickListener from window.location.origin to app.baseUrl for improved path handling. This fixes an issue where Backstage, when running on a subpath, was unable to handle non-Backstage URLs of the same origin correctly. + +- 1f40e6b: Add optional props to `TechDocCustomHome` to allow for more flexibility: + + ```tsx + import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; + //... + + const options = { emptyRowsWhenPaging: false }; + const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; + }; + const techDocsTabsConfig = [ + { + label: 'Recommended Documentation', + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + panelProps: { CustomHeader: () => }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + CustomHeader: () => + linkDestination: linkDestination, + }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ], + }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, + }, + ], + }, + ]; + + const AppRoutes = () => { + + ) => ({children})} + /> + } + /> + ; + }; + ``` + + Add new Grid option called `InfoCardGrid` which is a more customizable card option for the Docs grid. + + ```tsx + entity.metadata['external-docs']} + /> + ``` + + Expose existing `CustomDocsPanel` so that it can be used independently if desired. + + ```tsx + const panels: PanelConfig[] = [ + { + description: '', + filterPredicate: entity => {}, + panelType: 'InfoCardGrid', + title: 'Standards', + panelProps: { + CustomHeader: () => + linkDestination: linkDestination, + }, + }, + { + description: '', + filterPredicate: entity => {}, + panelType: 'DocsCardGrid', + title: 'Contribute', + }, + ]; + { + panels.map((config, index) => ( + + )); + } + ``` + +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. + +- Updated dependencies + - @backstage/plugin-search-react@1.8.6 + - @backstage/core-components@0.16.4 + - @backstage/plugin-catalog-react@1.15.2 + - @backstage/frontend-plugin-api@0.9.5 + - @backstage/integration-react@1.2.4 + - @backstage/core-compat-api@0.3.6 + - @backstage/core-plugin-api@1.10.4 + - @backstage/plugin-techdocs-react@1.2.14 + - @backstage/plugin-auth-react@0.1.12 + - @backstage/theme@0.6.4 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.16.1 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.9.5-next.3 + - @backstage/core-compat-api@0.3.6-next.3 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-components@0.16.4-next.1 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration@1.16.1 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/plugin-auth-react@0.1.12-next.1 + - @backstage/plugin-catalog-react@1.15.2-next.3 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.6-next.3 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.14-next.1 + +## 1.12.3-next.2 + +### Patch Changes + +- eb3d91a: Use the custom error page if provided for displaying errors instead of the default error page +- Updated dependencies + - @backstage/core-components@0.16.4-next.1 + - @backstage/plugin-catalog-react@1.15.2-next.2 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.3.6-next.2 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.9.5-next.2 + - @backstage/integration@1.16.1 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/plugin-auth-react@0.1.12-next.1 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.6-next.2 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.14-next.1 + +## 1.12.2-next.1 + +### Patch Changes + +- 524f0af: Add missing route ref to the `/alpha` entity content extension. +- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits. +- Updated dependencies + - @backstage/core-components@0.16.4-next.0 + - @backstage/frontend-plugin-api@0.9.5-next.1 + - @backstage/integration-react@1.2.4-next.0 + - @backstage/core-compat-api@0.3.6-next.1 + - @backstage/core-plugin-api@1.10.4-next.0 + - @backstage/plugin-techdocs-react@1.2.14-next.0 + - @backstage/plugin-catalog-react@1.15.2-next.1 + - @backstage/plugin-search-react@1.8.6-next.1 + - @backstage/plugin-auth-react@0.1.12-next.0 + - @backstage/theme@0.6.4-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.16.1 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.2-next.0 + +### Patch Changes + +- f4be934: Changed the base URL in addLinkClickListener from window.location.origin to app.baseUrl for improved path handling. This fixes an issue where Backstage, when running on a subpath, was unable to handle non-Backstage URLs of the same origin correctly. + +- 1f40e6b: Add optional props to `TechDocCustomHome` to allow for more flexibility: + + ```tsx + import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; + //... + + const options = { emptyRowsWhenPaging: false }; + const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; + }; + const techDocsTabsConfig = [ + { + label: 'Recommended Documentation', + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + panelProps: { CustomHeader: () => }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + CustomHeader: () => + linkDestination: linkDestination, + }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ], + }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, + }, + ], + }, + ]; + + const AppRoutes = () => { + + ) => ({children})} + /> + } + /> + ; + }; + ``` + + Add new Grid option called `InfoCardGrid` which is a more customizable card option for the Docs grid. + + ```tsx + entity.metadata['external-docs']} + /> + ``` + + Expose existing `CustomDocsPanel` so that it can be used independently if desired. + + ```tsx + const panels: PanelConfig[] = [ + { + description: '', + filterPredicate: entity => {}, + panelType: 'InfoCardGrid', + title: 'Standards', + panelProps: { + CustomHeader: () => + linkDestination: linkDestination, + }, + }, + { + description: '', + filterPredicate: entity => {}, + panelType: 'DocsCardGrid', + title: 'Contribute', + }, + ]; + { + panels.map((config, index) => ( + + )); + } + ``` + +- Updated dependencies + - @backstage/plugin-search-react@1.8.6-next.0 + - @backstage/frontend-plugin-api@0.9.5-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.3.6-next.0 + - @backstage/core-components@0.16.3 + - @backstage/core-plugin-api@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.16.1 + - @backstage/integration-react@1.2.3 + - @backstage/theme@0.6.3 + - @backstage/plugin-auth-react@0.1.11 + - @backstage/plugin-catalog-react@1.15.2-next.0 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.13 + +## 1.12.1 + +### Patch Changes + +- 3710b35: Allow passing down `withSearch` prop to `EntityTechdocsContent` component since it was `true` by default, now user can use the `EntityTechdocsContent` component _without_ showing the search field on top of the content. +- Updated dependencies + - @backstage/plugin-catalog-react@1.15.1 + - @backstage/frontend-plugin-api@0.9.4 + - @backstage/core-plugin-api@1.10.3 + - @backstage/core-components@0.16.3 + - @backstage/integration@1.16.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.3.5 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.3 + - @backstage/theme@0.6.3 + - @backstage/plugin-auth-react@0.1.11 + - @backstage/plugin-search-common@1.2.17 + - @backstage/plugin-search-react@1.8.5 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.13 + +## 1.12.1-next.1 + +### Patch Changes + +- 3710b35: Allow passing down `withSearch` prop to `EntityTechdocsContent` component since it was `true` by default, now user can use the `EntityTechdocsContent` component _without_ showing the search field on top of the content. +- Updated dependencies + - @backstage/frontend-plugin-api@0.9.4-next.0 + - @backstage/core-plugin-api@1.10.3-next.0 + - @backstage/core-compat-api@0.3.5-next.0 + - @backstage/plugin-catalog-react@1.15.1-next.1 + - @backstage/plugin-search-react@1.8.5-next.0 + - @backstage/core-components@0.16.3-next.0 + - @backstage/integration-react@1.2.3-next.0 + - @backstage/plugin-auth-react@0.1.11-next.0 + - @backstage/plugin-techdocs-react@1.2.13-next.0 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/plugin-search-common@1.2.17-next.0 + - @backstage/integration@1.16.1-next.0 + - @backstage/theme@0.6.3 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.15.1-next.0 + - @backstage/integration-react@1.2.2 + - @backstage/core-compat-api@0.3.4 + +## 1.12.0 + +### Minor Changes + +- e153ca6: Add pagination support to TechDocs Index Page and make it the default + +### Patch Changes + +- 7d8777d: Added support for the Search bar in docs residing in the entity page tab, and not only the global "/docs" page. +- Updated dependencies + - @backstage/plugin-catalog-react@1.15.0 + - @backstage/integration@1.16.0 + - @backstage/plugin-search-react@1.8.4 + - @backstage/core-compat-api@0.3.4 + - @backstage/frontend-plugin-api@0.9.3 + - @backstage/theme@0.6.3 + - @backstage/core-components@0.16.2 + - @backstage/errors@1.2.6 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/core-plugin-api@1.10.2 + - @backstage/integration-react@1.2.2 + - @backstage/plugin-auth-react@0.1.10 + - @backstage/plugin-search-common@1.2.16 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.12 + +## 1.11.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.3.4-next.2 + - @backstage/plugin-catalog-react@1.14.3-next.2 + - @backstage/errors@1.2.6-next.0 + - @backstage/catalog-model@1.7.2-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/core-components@0.16.2-next.2 + - @backstage/core-plugin-api@1.10.2-next.0 + - @backstage/frontend-plugin-api@0.9.3-next.2 + - @backstage/integration@1.16.0-next.1 + - @backstage/integration-react@1.2.2-next.1 + - @backstage/theme@0.6.3-next.0 + - @backstage/plugin-auth-react@0.1.10-next.2 + - @backstage/plugin-search-common@1.2.16-next.0 + - @backstage/plugin-search-react@1.8.4-next.2 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.12-next.2 + +## 1.11.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.14.3-next.1 + - @backstage/core-components@0.16.2-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/core-compat-api@0.3.4-next.1 + - @backstage/core-plugin-api@1.10.1 + - @backstage/errors@1.2.5 + - @backstage/frontend-plugin-api@0.9.3-next.1 + - @backstage/integration@1.16.0-next.0 + - @backstage/integration-react@1.2.2-next.0 + - @backstage/theme@0.6.3-next.0 + - @backstage/plugin-auth-react@0.1.10-next.1 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-search-react@1.8.4-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.12-next.1 + +## 1.11.3-next.0 + +### Patch Changes + +- 7d8777d: Added support for the Search bar in docs residing in the entity page tab, and not only the global "/docs" page. +- Updated dependencies + - @backstage/integration@1.16.0-next.0 + - @backstage/plugin-search-react@1.8.4-next.0 + - @backstage/plugin-catalog-react@1.14.3-next.0 + - @backstage/frontend-plugin-api@0.9.3-next.0 + - @backstage/theme@0.6.3-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/core-compat-api@0.3.4-next.0 + - @backstage/core-components@0.16.2-next.0 + - @backstage/core-plugin-api@1.10.1 + - @backstage/errors@1.2.5 + - @backstage/integration-react@1.2.2-next.0 + - @backstage/plugin-auth-react@0.1.10-next.0 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.12-next.0 + +## 1.11.1 + +### Patch Changes + +- 37a7810: Fixed an issue where `` would re-render infinitely under certain conditions. +- e937ae7: Fix an issue with index page of documentation site being re-rendered. +- 90246a9: Fix techdocs config schema for custom elements sanitizer +- 605bdc0: Avoid page re-rendering when clicking on anchor links in the same documentation page. +- 4f0cb89: Added DomPurify sanitizer configuration for custom elements implementing RFC . + See for how to enable it in the configuration. +- f246178: Removed `canvas` dev dependency. +- 4a2f73a: Fix an issue that caused the current documentation page to be re-rendered when navigating to + another one. +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/theme@0.6.1 + - @backstage/plugin-catalog-react@1.14.1 + - @backstage/core-components@0.16.0 + - @backstage/plugin-techdocs-react@1.2.10 + - @backstage/catalog-model@1.7.1 + - @backstage/core-compat-api@0.3.2 + - @backstage/core-plugin-api@1.10.1 + - @backstage/errors@1.2.5 + - @backstage/frontend-plugin-api@0.9.1 + - @backstage/integration@1.15.2 + - @backstage/integration-react@1.2.1 + - @backstage/plugin-auth-react@0.1.8 + - @backstage/plugin-search-common@1.2.15 + - @backstage/plugin-search-react@1.8.2 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.11.1-next.3 + +### Patch Changes + +- e937ae7: Fix an issue with index page of documentation site being re-rendered. +- Updated dependencies + - @backstage/core-components@0.16.0-next.2 + - @backstage/plugin-catalog-react@1.14.1-next.3 + - @backstage/core-compat-api@0.3.2-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.10.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.9.1-next.2 + - @backstage/integration@1.15.1 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.1-next.0 + - @backstage/plugin-auth-react@0.1.8-next.2 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.2 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.10-next.2 + +## 1.11.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.14.1-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.3.2-next.1 + - @backstage/core-components@0.16.0-next.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.9.1-next.1 + - @backstage/integration@1.15.1 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.1-next.0 + - @backstage/plugin-auth-react@0.1.8-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.10-next.1 + +## 1.11.1-next.1 + +### Patch Changes + +- 90246a9: Fix techdocs config schema for custom elements sanitizer +- Updated dependencies + - @backstage/theme@0.6.1-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.3.2-next.1 + - @backstage/core-components@0.16.0-next.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.9.1-next.1 + - @backstage/integration@1.15.1 + - @backstage/integration-react@1.2.0 + - @backstage/plugin-auth-react@0.1.8-next.1 + - @backstage/plugin-catalog-react@1.14.1-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.10-next.1 + +## 1.11.1-next.0 + +### Patch Changes + +- 605bdc0: Avoid page re-rendering when clicking on anchor links in the same documentation page. +- 4f0cb89: Added DomPurify sanitizer configuration for custom elements implementing RFC . + See for how to enable it in the configuration. +- f246178: Removed `canvas` dev dependency. +- 4a2f73a: Fix an issue that caused the current documentation page to be re-rendered when navigating to + another one. +- Updated dependencies + - @backstage/core-components@0.16.0-next.0 + - @backstage/plugin-techdocs-react@1.2.10-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.3.2-next.0 + - @backstage/core-plugin-api@1.10.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.9.1-next.0 + - @backstage/integration@1.15.1 + - @backstage/integration-react@1.2.0 + - @backstage/theme@0.6.0 + - @backstage/plugin-auth-react@0.1.8-next.0 + - @backstage/plugin-catalog-react@1.14.1-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.2-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.11.0 + +### Minor Changes + +- e77ff3d: Adds support for custom background colors in code blocks and inline code within TechDocs. + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- a77cb40: Make `emptyState` input optional on `entity-content:techdocs` extension so that + the default empty state extension works correctly. +- e918061: Add support for mkdocs material palette conditional hashes. +- 720a2f9: Updated dependency `git-url-parse` to `^15.0.0`. +- e8b4966: Use more of the available space for the navigation sidebar. +- Updated dependencies + - @backstage/core-components@0.15.1 + - @backstage/frontend-plugin-api@0.9.0 + - @backstage/integration-react@1.2.0 + - @backstage/core-compat-api@0.3.1 + - @backstage/core-plugin-api@1.10.0 + - @backstage/plugin-techdocs-react@1.2.9 + - @backstage/plugin-catalog-react@1.14.0 + - @backstage/plugin-search-react@1.8.1 + - @backstage/plugin-auth-react@0.1.7 + - @backstage/theme@0.6.0 + - @backstage/integration@1.15.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.11.0-next.2 + +### Minor Changes + +- e77ff3d: Adds support for custom background colors in code blocks and inline code within TechDocs. + +### Patch Changes + +- e918061: Add support for mkdocs material palette conditional hashes. +- 720a2f9: Updated dependency `git-url-parse` to `^15.0.0`. +- e8b4966: Use more of the available space for the navigation sidebar. +- Updated dependencies + - @backstage/plugin-catalog-react@1.14.0-next.2 + - @backstage/integration@1.15.1-next.1 + - @backstage/theme@0.6.0-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.3.1-next.2 + - @backstage/core-components@0.15.1-next.2 + - @backstage/core-plugin-api@1.10.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.9.0-next.2 + - @backstage/integration-react@1.2.0-next.2 + - @backstage/plugin-auth-react@0.1.7-next.2 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.1-next.2 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.9-next.2 + +## 1.10.11-next.1 + +### Patch Changes + +- e969dc7: Move `@types/react` to a peer dependency. +- Updated dependencies + - @backstage/core-components@0.15.1-next.1 + - @backstage/frontend-plugin-api@0.9.0-next.1 + - @backstage/integration-react@1.2.0-next.1 + - @backstage/core-compat-api@0.3.1-next.1 + - @backstage/core-plugin-api@1.10.0-next.1 + - @backstage/plugin-techdocs-react@1.2.9-next.1 + - @backstage/plugin-catalog-react@1.14.0-next.1 + - @backstage/plugin-search-react@1.8.1-next.1 + - @backstage/plugin-auth-react@0.1.7-next.1 + - @backstage/theme@0.5.8-next.0 + - @backstage/integration@1.15.1-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.10.10-next.0 + +### Patch Changes + +- a77cb40: Make `emptyState` input optional on `entity-content:techdocs` extension so that + the default empty state extension works correctly. +- Updated dependencies + - @backstage/frontend-plugin-api@0.9.0-next.0 + - @backstage/core-compat-api@0.3.1-next.0 + - @backstage/core-components@0.15.1-next.0 + - @backstage/core-plugin-api@1.10.0-next.0 + - @backstage/plugin-catalog-react@1.13.1-next.0 + - @backstage/plugin-search-react@1.8.1-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.15.0 + - @backstage/integration-react@1.1.32-next.0 + - @backstage/theme@0.5.7 + - @backstage/plugin-auth-react@0.1.7-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.9-next.0 + +## 1.10.9 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. + +- fe94ad8: Fixes left navigation positioning when using mkdocs blog plugin + +- b0206dc: Added support for setting page status with 'new' and 'deprecated' values, allowing visual indication of page status in TechDocs. To use include the following at the top of your markdown file: + + ```markdown + --- + status: new + --- + ``` + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. + +- c7cb4c0: Add `empty-state:techdocs/entity-content` extension to allow overriding the empty state for the entity page techdocs tab. + +- 97db53e: Enhanced the table hover effect with a lighter color and updated the border radius to align with Backstage's theme styling + +- Updated dependencies + - @backstage/core-components@0.15.0 + - @backstage/plugin-catalog-react@1.13.0 + - @backstage/frontend-plugin-api@0.8.0 + - @backstage/plugin-techdocs-react@1.2.8 + - @backstage/core-compat-api@0.3.0 + - @backstage/plugin-search-react@1.8.0 + - @backstage/integration-react@1.1.31 + - @backstage/catalog-model@1.7.0 + - @backstage/integration@1.15.0 + - @backstage/core-plugin-api@1.9.4 + - @backstage/theme@0.5.7 + - @backstage/plugin-auth-react@0.1.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.10.9-next.2 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 97db53e: Enhanced the table hover effect with a lighter color and updated the border radius to align with Backstage's theme styling +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-auth-react@0.1.6-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.10.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## 1.10.9-next.0 + +### Patch Changes + +- fec8b57: Updated exports to use the new type parameters for extensions and extension blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.0 + - @backstage/core-compat-api@0.2.9-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.0 + - @backstage/plugin-search-react@1.8.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.10.8 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 4490d73: Refactor TechDocs' mkdocs-redirects support. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + +## 1.10.8-next.3 + +### Patch Changes + +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## 1.10.8-next.2 + +### Patch Changes + +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 1.10.8-next.1 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 1.10.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 1.10.7 + +### Patch Changes + +- 8fc2622: Fixed an issue that was causing techdocs pages unnecessarily re-render on navigate. +- 6fa652c: Improve default sorting of docs table +- 605b691: Allow for searching TechDocs by entity title +- 60caa92: Fix double scrollbar bug in reader +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-search-react@1.7.13 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + +## 1.10.7-next.2 + +### Patch Changes + +- 6fa652c: Improve default sorting of docs table +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## 1.10.7-next.1 + +### Patch Changes + +- 60caa92: Fix double scrollbar bug in reader +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## 1.10.7-next.0 + +### Patch Changes + +- 8ac9ce5: Fixed a bug with the TechDocsReaderPageProvider not re-rendering when setShadowDom is called, meaning that the useShadowDom hooks were inconsistent. This issue caused the TextSize addon changes not to reapply during navigation. +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.6-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/plugin-search-common@1.2.12 + +## 1.10.6 + +### Patch Changes + +- 654af4a: mkdocs-material have updated their CSS variable template, and a few are unset in Backstage. This patch adds the missing variables to ensure coverage. +- cbebad1: Internal updates to allow reusing Backstage's `fetchApi` implementation for event source requests. This allows you to for example, override the `Authorization` header. +- 96cd13e: `TechDocsIndexPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- e40bd9a: Fixed bug in `CopyToClipboardButton` component where positioning of the "Copy to clipboard" button in techdocs code snippets was broken in some cases. +- d44a20a: Added additional plugin metadata to `package.json`. +- 1256d88: Fixed an issue preventing the `TechDocsSearchBar` component from opening when clicking on the arrow icon. +- Updated dependencies + - @backstage/core-components@0.14.8 + - @backstage/core-compat-api@0.2.6 + - @backstage/integration@1.12.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.5 + - @backstage/plugin-catalog-react@1.12.1 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-search-react@1.7.12 + - @backstage/plugin-auth-react@0.1.3 + - @backstage/integration-react@1.1.28 + - @backstage/frontend-plugin-api@0.6.6 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 1.10.6-next.2 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/core-components@0.14.8-next.2 + - @backstage/integration@1.12.0-next.1 + - @backstage/plugin-techdocs-react@1.2.5-next.2 + - @backstage/plugin-catalog-react@1.12.1-next.2 + - @backstage/plugin-search-common@1.2.12-next.0 + - @backstage/plugin-search-react@1.7.12-next.2 + - @backstage/plugin-auth-react@0.1.3-next.2 + - @backstage/integration-react@1.1.28-next.1 + - @backstage/frontend-plugin-api@0.6.6-next.2 + - @backstage/core-compat-api@0.2.6-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3-next.0 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6-next.0 + +## 1.10.6-next.1 + +### Patch Changes + +- cbebad1: Internal updates to allow reusing Backstage's `fetchApi` implementation for event source requests. This allows you to for example, override the `Authorization` header. +- Updated dependencies + - @backstage/core-components@0.14.8-next.1 + - @backstage/core-compat-api@0.2.6-next.1 + - @backstage/core-plugin-api@1.9.3-next.0 + - @backstage/integration@1.12.0-next.0 + - @backstage/frontend-plugin-api@0.6.6-next.1 + - @backstage/integration-react@1.1.28-next.0 + - @backstage/plugin-auth-react@0.1.3-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.1 + - @backstage/plugin-search-react@1.7.12-next.1 + - @backstage/plugin-techdocs-react@1.2.5-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-search-common@1.2.11 + +## 1.10.6-next.0 + +### Patch Changes + +- 654af4a: mkdocs-material have updated their CSS variable template, and a few are unset in Backstage. This patch adds the missing variables to ensure coverage. +- 96cd13e: `TechDocsIndexPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- e40bd9a: Fixed bug in CopyToClipboardButton component where positioning of the "Copy to clipboard" button in techdocs code snippets was broken in some cases +- 1256d88: Fix weird opening behaviour of the component. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-search-common@1.2.11 + +## 1.10.5 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/integration@1.11.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-techdocs-react@1.2.4 + +## 1.10.5-next.2 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## 1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## 1.10.5-next.0 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + +## 1.10.4 + +### Patch Changes + +- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`. +- cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`. +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.3 + - @backstage/plugin-search-react@1.7.10 + - @backstage/plugin-auth-react@0.1.0 + - @backstage/plugin-catalog-react@1.11.3 + - @backstage/core-compat-api@0.2.4 + - @backstage/core-components@0.14.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.4 + - @backstage/theme@0.5.3 + - @backstage/integration-react@1.1.26 + - @backstage/integration@1.10.0 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.11 + +## 1.10.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-react@0.1.0-next.1 + - @backstage/frontend-plugin-api@0.6.4-next.1 + - @backstage/core-compat-api@0.2.4-next.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.4-next.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0-next.0 + - @backstage/integration-react@1.1.26-next.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-catalog-react@1.11.3-next.1 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.10-next.1 + - @backstage/plugin-techdocs-react@1.2.3-next.0 + +## 1.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.10.0-next.0 + - @backstage/core-components@0.14.4-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.4-next.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.4-next.0 + - @backstage/integration-react@1.1.26-next.0 + - @backstage/theme@0.5.2 + - @backstage/plugin-auth-react@0.0.4-next.0 + - @backstage/plugin-catalog-react@1.11.3-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.10-next.0 + - @backstage/plugin-techdocs-react@1.2.3-next.0 + +## 1.10.3 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/core-components@0.14.3 + - @backstage/plugin-techdocs-react@1.2.2 + - @backstage/plugin-catalog-react@1.11.2 + - @backstage/plugin-search-react@1.7.9 + - @backstage/frontend-plugin-api@0.6.3 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-auth-react@0.0.3 + - @backstage/core-compat-api@0.2.3 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/plugin-search-common@1.2.11 + +## 1.10.2 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/core-components@0.14.2 + - @backstage/plugin-techdocs-react@1.2.1 + - @backstage/plugin-catalog-react@1.11.1 + - @backstage/plugin-search-react@1.7.8 + - @backstage/frontend-plugin-api@0.6.2 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-auth-react@0.0.2 + - @backstage/core-compat-api@0.2.2 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/plugin-search-common@1.2.11 + +## 1.10.1 + +### Patch Changes + +- 7c2d022: Fixed bug in TechDocs sidebar render that prevented scrollbar from being displayed +- 3f14e9f: Implement a client cookie refresh mechanism. +- 62bcaf8: Use the new generic refresh user cookie provider. +- 28f27f0: Added ESLint rule `no-top-level-material-ui-4-imports` to aid with the migration to Material UI v5. +- Updated dependencies + - @backstage/integration@1.9.1 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-react@0.0.1 + - @backstage/theme@0.5.2 + - @backstage/integration-react@1.1.25 + - @backstage/plugin-techdocs-react@1.2.0 + - @backstage/plugin-catalog-react@1.11.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/catalog-model@1.4.5 + - @backstage/core-compat-api@0.2.1 + - @backstage/core-plugin-api@1.9.1 + - @backstage/frontend-plugin-api@0.6.1 + - @backstage/plugin-search-react@1.7.7 + +## 1.10.1-next.2 + +### Patch Changes + +- 7c2d022: Fixed bug in TechDocs sidebar render that prevented scrollbar from being displayed +- 3f14e9f: Implement a client cookie refresh mechanism. +- Updated dependencies + - @backstage/integration@1.9.1-next.2 + - @backstage/plugin-techdocs-react@1.2.0-next.2 + - @backstage/core-components@0.14.1-next.2 + - @backstage/plugin-catalog-react@1.11.0-next.2 + - @backstage/integration-react@1.1.25-next.2 + - @backstage/frontend-plugin-api@0.6.1-next.2 + - @backstage/plugin-search-react@1.7.7-next.2 + - @backstage/core-compat-api@0.2.1-next.2 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.2.0-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 1.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/core-components@0.14.1-next.1 + - @backstage/plugin-catalog-react@1.10.1-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/integration@1.9.1-next.1 + - @backstage/integration-react@1.1.25-next.1 + - @backstage/plugin-techdocs-react@1.1.17-next.1 + - @backstage/frontend-plugin-api@0.6.1-next.1 + - @backstage/plugin-search-react@1.7.7-next.1 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/core-compat-api@0.2.1-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/plugin-search-common@1.2.11-next.1 + +## 1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/core-components@0.14.1-next.0 + - @backstage/integration-react@1.1.25-next.0 + - @backstage/plugin-catalog-react@1.10.1-next.0 + - @backstage/plugin-search-common@1.2.11-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/core-plugin-api@1.9.1-next.0 + - @backstage/integration@1.9.1-next.0 + - @backstage/plugin-search-react@1.7.7-next.0 + - @backstage/plugin-techdocs-react@1.1.17-next.0 + - @backstage/frontend-plugin-api@0.6.1-next.0 + - @backstage/core-compat-api@0.2.1-next.0 + +## 1.10.0 + +### Minor Changes + +- af4d147: Updated the styling for `` tags to avoid word break. + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 3631fb4: Updated dependency `dompurify` to `^3.0.0`. + Updated dependency `@types/dompurify` to `^3.0.0`. +- 1cae748: Updated dependency `git-url-parse` to `^14.0.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.0 + - @backstage/core-compat-api@0.2.0 + - @backstage/plugin-catalog-react@1.10.0 + - @backstage/core-components@0.14.0 + - @backstage/plugin-techdocs-react@1.1.16 + - @backstage/catalog-model@1.4.4 + - @backstage/theme@0.5.1 + - @backstage/integration@1.9.0 + - @backstage/core-plugin-api@1.9.0 + - @backstage/plugin-search-react@1.7.6 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.24 + - @backstage/plugin-search-common@1.2.10 + +## 1.10.0-next.3 + +### Patch Changes + +- 3631fb4: Updated dependency `dompurify` to `^3.0.0`. + Updated dependency `@types/dompurify` to `^3.0.0`. +- 1cae748: Updated dependency `git-url-parse` to `^14.0.0`. +- Updated dependencies + - @backstage/theme@0.5.1-next.1 + - @backstage/integration@1.9.0-next.1 + - @backstage/core-components@0.14.0-next.2 + - @backstage/plugin-catalog-react@1.10.0-next.3 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.2.0-next.3 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.6.0-next.3 + - @backstage/integration-react@1.1.24-next.2 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-search-react@1.7.6-next.3 + - @backstage/plugin-techdocs-react@1.1.16-next.2 + +## 1.10.0-next.2 + +### Patch Changes + +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- Updated dependencies + - @backstage/core-components@0.14.0-next.1 + - @backstage/plugin-techdocs-react@1.1.16-next.1 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/frontend-plugin-api@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.6-next.2 + - @backstage/theme@0.5.1-next.0 + - @backstage/integration-react@1.1.24-next.1 + - @backstage/core-compat-api@0.2.0-next.2 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/errors@1.2.3 + - @backstage/integration@1.9.0-next.0 + - @backstage/plugin-search-common@1.2.10 + +## 1.10.0-next.1 + +### Minor Changes + +- af4d147: Updated the styling for `` tags to avoid word break. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.0-next.1 + - @backstage/core-compat-api@0.2.0-next.1 + - @backstage/core-components@0.14.0-next.0 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/core-plugin-api@1.8.3-next.0 + - @backstage/integration@1.9.0-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.1 + - @backstage/plugin-search-react@1.7.6-next.1 + - @backstage/integration-react@1.1.24-next.0 + - @backstage/plugin-techdocs-react@1.1.16-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.10 + +## 1.9.4-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## 1.9.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.10 + +## 1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.9 + +## 1.9.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.9 + +## 1.9.2 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.9 + +## 1.9.2-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## 1.9.2-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## 1.9.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-search-common@1.2.8 + +## 1.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.0.1-next.0 + +## 1.9.2-next.0 + +### Patch Changes + +- 03d0b6dcdc: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- Updated dependencies + - @backstage/core-compat-api@0.0.2-next.0 + - @backstage/core-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-react@1.9.2-next.0 + - @backstage/core-components@0.13.9-next.0 + - @backstage/plugin-search-react@1.7.4-next.0 + - @backstage/integration@1.8.0-next.0 + - @backstage/theme@0.5.0-next.0 + - @backstage/frontend-plugin-api@0.3.1-next.0 + - @backstage/integration-react@1.1.22-next.0 + - @backstage/plugin-techdocs-react@1.1.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.8 + +## 1.9.0 + +### Minor Changes + +- 17f93d5589: A new analytics event `not-found` will be published when a user visits a documentation site that does not exist + +### Patch Changes + +- 4728b3960d: Fixed navigation bug that caused users to not be scrolled to the top of a new page. Fixed navigation bug where using backwards and forwards browser navigation did not scroll users to the correct place on the TechDoc page. +- a3add7a682: Export alpha routes and nav item extension, only available for applications that uses the new Frontend system. +- 71c97e7d73: The \`spec.lifecycle' field in entities will now always be rendered as a string. +- 68fc9dc60e: Updated alpha exports according to routing changes in `@backstage/frontend-plugin-api`. +- 6c2b872153: Add official support for React 18. +- 0bf6ebda88: Added entity page content for the new plugin exported via `/alpha`. +- 67cc85bb14: Switched the conditional `react-dom/client` import to use `import(...)` rather than `require(...)`. +- 4aa43f62aa: Updated dependency `cross-fetch` to `^4.0.0`. +- 38cda52746: Added support for React 18. The new `createRoot` API from `react-dom/client` will now be used if present. +- fdb5e23602: Import `MissingAnnotationEmptyState` from `@backstage/plugin-catalog-react` to remove the cyclical dependency +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.0 + - @backstage/core-components@0.13.8 + - @backstage/frontend-plugin-api@0.3.0 + - @backstage/integration@1.7.2 + - @backstage/integration-react@1.1.21 + - @backstage/core-plugin-api@1.8.0 + - @backstage/plugin-techdocs-react@1.1.13 + - @backstage/plugin-search-react@1.7.2 + - @backstage/theme@0.4.4 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.8 + +## 1.9.0-next.2 + +### Minor Changes + +- [#20851](https://github.com/backstage/backstage/pull/20851) [`17f93d5589`](https://github.com/backstage/backstage/commit/17f93d5589812df3dea53d956212e184b080fbac) Thanks [@agentbellnorm](https://github.com/agentbellnorm)! - A new analytics event `not-found` will be published when a user visits a documentation site that does not exist + +### Patch Changes + +- [#20842](https://github.com/backstage/backstage/pull/20842) [`fdb5e23602`](https://github.com/backstage/backstage/commit/fdb5e2360299c5faa30f4d4236fc548b94d37446) Thanks [@benjdlambert](https://github.com/benjdlambert)! - Import `MissingAnnotationEmptyState` from `@backstage/plugin-catalog-react` to remove the cyclical dependency + +- Updated dependencies + - @backstage/core-components@0.13.8-next.2 + - @backstage/frontend-plugin-api@0.3.0-next.2 + - @backstage/plugin-catalog-react@1.9.0-next.2 + - @backstage/integration-react@1.1.21-next.1 + - @backstage/plugin-search-react@1.7.2-next.2 + - @backstage/plugin-techdocs-react@1.1.13-next.2 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.7.2-next.0 + - @backstage/frontend-plugin-api@0.3.0-next.1 + - @backstage/plugin-catalog-react@1.9.0-next.1 + - @backstage/plugin-search-react@1.7.2-next.1 + - @backstage/integration-react@1.1.21-next.1 + - @backstage/core-components@0.13.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/theme@0.4.4-next.0 + - @backstage/plugin-search-common@1.2.7 + - @backstage/plugin-techdocs-react@1.1.13-next.1 + +## 1.8.1-next.0 + +### Patch Changes + +- 4728b3960d: Fixed navigation bug that caused users to not be scrolled to the top of a new page. Fixed navigation bug where using backwards and forwards browser navigation did not scroll users to the correct place on the TechDoc page. +- a3add7a682: Export alpha routes and nav item extension, only available for applications that uses the new Frontend system. +- 71c97e7d73: The \`spec.lifecycle' field in entities will now always be rendered as a string. +- 68fc9dc60e: Updated alpha exports according to routing changes in `@backstage/frontend-plugin-api`. +- 6c2b872153: Add official support for React 18. +- 0bf6ebda88: Added entity page content for the new plugin exported via `/alpha`. +- 67cc85bb14: Switched the conditional `react-dom/client` import to use `import(...)` rather than `require(...)`. +- 38cda52746: Added support for React 18. The new `createRoot` API from `react-dom/client` will now be used if present. +- Updated dependencies + - @backstage/core-components@0.13.7-next.0 + - @backstage/frontend-plugin-api@0.3.0-next.0 + - @backstage/plugin-catalog-react@1.9.0-next.0 + - @backstage/integration-react@1.1.21-next.0 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/plugin-techdocs-react@1.1.13-next.0 + - @backstage/plugin-search-react@1.7.2-next.0 + - @backstage/theme@0.4.4-next.0 + - @backstage/integration@1.7.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.7 + +## 1.8.0 + +### Minor Changes + +- 27740caa2d: Added experimental support for declarative integration via the `/alpha` subpath. + +### Patch Changes + +- 4918f65ab2: Create an experimental `TechDocsSearchResultItemExtension` for declarative integration with Backstage; it can be accessed via the `/alpha` import. +- 3605370af6: Improved `DocsTable` to display pagination controls dynamically, appearing only when needed. +- 0296f272b4: The \`spec.lifecycle' field in entities will now always be rendered as a string. +- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`. +- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`. +- 9468a67b92: Added support for React 18. The new `createRoot` API from `react-dom/client` will now be used if present. +- df449a7a31: Add kind column by default to TechDocsTable +- Updated dependencies + - @backstage/integration@1.7.1 + - @backstage/plugin-catalog-react@1.8.5 + - @backstage/frontend-plugin-api@0.2.0 + - @backstage/core-plugin-api@1.7.0 + - @backstage/core-components@0.13.6 + - @backstage/integration-react@1.1.20 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-react@1.7.1 + - @backstage/plugin-techdocs-react@1.1.12 + - @backstage/theme@0.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.7 + +## 1.7.1-next.2 + +### Patch Changes + +- 3605370af6: Improved `DocsTable` to display pagination controls dynamically, appearing only when needed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.2.0-next.2 + - @backstage/integration-react@1.1.20-next.2 + - @backstage/core-components@0.13.6-next.2 + - @backstage/core-plugin-api@1.7.0-next.1 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.2 + - @backstage/integration@1.7.1-next.1 + - @backstage/errors@1.2.3-next.0 + - @backstage/plugin-search-react@1.7.1-next.2 + - @backstage/theme@0.4.3-next.0 + - @backstage/config@1.1.1-next.0 + - @backstage/plugin-search-common@1.2.7-next.0 + - @backstage/plugin-techdocs-react@1.1.12-next.2 + +## 1.7.1-next.1 + +### Patch Changes + +- 4918f65ab2: Create an experimental `TechDocsSearchResultItemExtension` for declarative integration with Backstage; it can be accessed via the `/alpha` import. +- df449a7a31: Add kind column by default to TechDocsTable +- Updated dependencies + - @backstage/frontend-plugin-api@0.1.1-next.1 + - @backstage/core-components@0.13.6-next.1 + - @backstage/plugin-search-react@1.7.1-next.1 + - @backstage/integration-react@1.1.20-next.1 + - @backstage/plugin-catalog-react@1.8.5-next.1 + - @backstage/plugin-techdocs-react@1.1.12-next.1 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/config@1.1.0 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/integration@1.7.1-next.0 + - @backstage/theme@0.4.2 + - @backstage/plugin-search-common@1.2.6 + +## 1.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.7.1-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.0 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/core-components@0.13.6-next.0 + - @backstage/integration-react@1.1.20-next.0 + - @backstage/config@1.1.0 + - @backstage/plugin-search-react@1.7.1-next.0 + - @backstage/plugin-techdocs-react@1.1.12-next.0 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/theme@0.4.2 + - @backstage/plugin-search-common@1.2.6 + +## 1.7.0 + +### Minor Changes + +- e44f45ac4515: This change allows a new annotation of `backstage.io/techdocs-entity` this ref allows you to reference another entity for its TechDocs. This allows you have a single TechDoc for all items in a system, for example you might have a frontend and a backend in the same repo. This would allow you to have TechDocs build under a `System` entity while referencing the system e.g.: `backstage.io/techdocs-entity: system:default/example` that will show the systems docs in both the TechDocs button and the TechDocs tab without needing to do duplicate builds and filling the TechDocs page with garbage. + +### Patch Changes + +- 88c9525a36f3: Fixed bug in styles that caused next and previous links in footer to overlap page content. +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/integration-react@1.1.19 + - @backstage/plugin-catalog-react@1.8.4 + - @backstage/core-components@0.13.5 + - @backstage/config@1.1.0 + - @backstage/catalog-model@1.4.2 + - @backstage/core-plugin-api@1.6.0 + - @backstage/errors@1.2.2 + - @backstage/integration@1.7.0 + - @backstage/plugin-search-common@1.2.6 + - @backstage/plugin-search-react@1.7.0 + - @backstage/plugin-techdocs-react@1.1.10 + - @backstage/theme@0.4.2 + +## 1.7.0-next.3 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- Updated dependencies + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/config@1.1.0-next.2 + - @backstage/core-components@0.13.5-next.3 + - @backstage/core-plugin-api@1.6.0-next.3 + - @backstage/errors@1.2.2-next.0 + - @backstage/integration@1.7.0-next.3 + - @backstage/integration-react@1.1.19-next.3 + - @backstage/plugin-catalog-react@1.8.4-next.3 + - @backstage/plugin-search-common@1.2.6-next.2 + - @backstage/plugin-search-react@1.7.0-next.3 + - @backstage/plugin-techdocs-react@1.1.10-next.3 + - @backstage/theme@0.4.2-next.0 + +## 1.7.0-next.2 + +### Minor Changes + +- e44f45ac4515: This change allows a new annotation of `backstage.io/techdocs-entity` this ref allows you to reference another entity for its TechDocs. This allows you have a single TechDoc for all items in a system, for example you might have a frontend and a backend in the same repo. This would allow you to have TechDocs build under a `System` entity while referencing the system e.g.: `backstage.io/techdocs-entity: system:default/example` that will show the systems docs in both the TechDocs button and the TechDocs tab without needing to do duplicate builds and filling the TechDocs page with garbage. + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/integration-react@1.1.19-next.2 + - @backstage/core-components@0.13.5-next.2 + - @backstage/core-plugin-api@1.6.0-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/plugin-catalog-react@1.8.4-next.2 + - @backstage/plugin-search-react@1.7.0-next.2 + - @backstage/plugin-techdocs-react@1.1.10-next.2 + - @backstage/integration@1.7.0-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-search-common@1.2.6-next.1 + +## 1.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.4-next.1 + - @backstage/core-components@0.13.5-next.1 + - @backstage/config@1.1.0-next.0 + - @backstage/integration@1.7.0-next.1 + - @backstage/plugin-search-react@1.7.0-next.1 + - @backstage/integration-react@1.1.19-next.1 + - @backstage/plugin-techdocs-react@1.1.10-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/core-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-search-common@1.2.6-next.0 + +## 1.6.8-next.0 + +### Patch Changes + +- 88c9525a36f3: Fixed bug in styles that caused next and previous links in footer to overlap page content. +- Updated dependencies + - @backstage/integration-react@1.1.18-next.0 + - @backstage/integration@1.7.0-next.0 + - @backstage/core-plugin-api@1.6.0-next.0 + - @backstage/core-components@0.13.5-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-react@1.8.3-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.5-next.0 + - @backstage/plugin-techdocs-react@1.1.10-next.0 + +## 1.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.1.16 + - @backstage/integration@1.6.0 + - @backstage/core-components@0.13.4 + - @backstage/plugin-catalog-react@1.8.1 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-search-react@1.6.4 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-techdocs-react@1.1.9 + +## 1.6.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.1-next.1 + - @backstage/integration-react@1.1.16-next.1 + +## 1.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.1.16-next.1 + - @backstage/integration@1.5.1 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/core-components@0.13.4-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.4-next.0 + - @backstage/plugin-techdocs-react@1.1.9-next.0 + +## 1.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.4-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/plugin-search-react@1.6.4-next.0 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/integration@1.5.1 + - @backstage/integration-react@1.1.16-next.0 + - @backstage/theme@0.4.1 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-techdocs-react@1.1.9-next.0 + +## 1.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.4.1 + - @backstage/errors@1.2.1 + - @backstage/plugin-catalog-react@1.8.0 + - @backstage/core-components@0.13.3 + - @backstage/core-plugin-api@1.5.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/integration@1.5.1 + - @backstage/integration-react@1.1.15 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-search-react@1.6.3 + - @backstage/plugin-techdocs-react@1.1.8 + +## 1.6.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.0-next.2 + - @backstage/theme@0.4.1-next.1 + - @backstage/core-plugin-api@1.5.3-next.1 + - @backstage/core-components@0.13.3-next.2 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1-next.0 + - @backstage/integration@1.5.1-next.0 + - @backstage/integration-react@1.1.15-next.2 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-search-react@1.6.3-next.2 + - @backstage/plugin-techdocs-react@1.1.8-next.2 + +## 1.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.4.1-next.0 + - @backstage/core-components@0.13.3-next.1 + - @backstage/core-plugin-api@1.5.3-next.0 + - @backstage/integration-react@1.1.15-next.1 + - @backstage/plugin-catalog-react@1.7.1-next.1 + - @backstage/plugin-search-react@1.6.3-next.1 + - @backstage/plugin-techdocs-react@1.1.8-next.1 + - @backstage/config@1.0.8 + +## 1.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/core-components@0.13.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/core-plugin-api@1.5.2 + - @backstage/integration@1.5.1-next.0 + - @backstage/integration-react@1.1.15-next.0 + - @backstage/theme@0.4.0 + - @backstage/plugin-catalog-react@1.7.1-next.0 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-search-react@1.6.3-next.0 + - @backstage/plugin-techdocs-react@1.1.8-next.0 + +## 1.6.4 + +### Patch Changes + +- 2f660eb573cc: Fix SearchBar styles & update StoryBook stories for custom styles for `notchedOutline` class. +- 956d09e8ea68: Change deprecated local references to import from shared `plugin-techdocs-react` plugin +- e33beb1f2a8e: Make the documentation pages printable (also handy for exporting to PDF) +- Updated dependencies + - @backstage/core-plugin-api@1.5.2 + - @backstage/plugin-search-react@1.6.2 + - @backstage/core-components@0.13.2 + - @backstage/theme@0.4.0 + - @backstage/integration@1.5.0 + - @backstage/plugin-catalog-react@1.7.0 + - @backstage/catalog-model@1.4.0 + - @backstage/errors@1.2.0 + - @backstage/plugin-techdocs-react@1.1.7 + - @backstage/integration-react@1.1.14 + - @backstage/config@1.0.8 + - @backstage/plugin-search-common@1.2.4 + +## 1.6.4-next.3 + +### Patch Changes + +- e33beb1f2a8e: Make the documentation pages printable (also handy for exporting to PDF) +- Updated dependencies + - @backstage/plugin-search-react@1.6.2-next.3 + - @backstage/core-components@0.13.2-next.3 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/errors@1.2.0-next.0 + - @backstage/integration@1.5.0-next.0 + - @backstage/integration-react@1.1.14-next.3 + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.3 + - @backstage/plugin-search-common@1.2.4-next.0 + - @backstage/plugin-techdocs-react@1.1.7-next.3 + +## 1.6.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.2 + - @backstage/core-components@0.13.2-next.2 + - @backstage/integration-react@1.1.14-next.2 + - @backstage/plugin-search-react@1.6.1-next.2 + - @backstage/plugin-techdocs-react@1.1.7-next.2 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.2-next.0 + +## 1.6.3-next.1 + +### Patch Changes + +- 2f660eb573cc: Fix SearchBar styles & update StoryBook stories for custom styles for `notchedOutline` class. +- Updated dependencies + - @backstage/integration@1.5.0-next.0 + - @backstage/errors@1.2.0-next.0 + - @backstage/plugin-search-react@1.6.1-next.1 + - @backstage/core-components@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/integration-react@1.1.14-next.1 + - @backstage/plugin-techdocs-react@1.1.7-next.1 + - @backstage/config@1.0.7 + - @backstage/theme@0.4.0-next.0 + - @backstage/plugin-search-common@1.2.4-next.0 + +## 1.6.3-next.0 + +### Patch Changes + +- 956d09e8ea68: Change deprecated local references to import from shared `plugin-techdocs-react` plugin +- Updated dependencies + - @backstage/plugin-catalog-react@1.7.0-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/plugin-techdocs-react@1.1.7-next.0 + - @backstage/integration@1.4.5 + - @backstage/config@1.0.7 + - @backstage/core-components@0.13.2-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/integration-react@1.1.14-next.0 + - @backstage/plugin-search-react@1.6.1-next.0 + - @backstage/catalog-model@1.3.0 + - @backstage/errors@1.1.5 + - @backstage/plugin-search-common@1.2.3 + +## 1.6.2 + +### Patch Changes + +- 863beb49498: Re-add the possibility to have trailing slashes in Techdocs navigation. +- Updated dependencies + - @backstage/theme@0.3.0 + - @backstage/plugin-catalog-react@1.6.0 + - @backstage/integration@1.4.5 + - @backstage/plugin-search-react@1.6.0 + - @backstage/core-components@0.13.1 + - @backstage/integration-react@1.1.13 + - @backstage/plugin-techdocs-react@1.1.6 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1 + - @backstage/errors@1.1.5 + - @backstage/plugin-search-common@1.2.3 + +## 1.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.3.0-next.0 + - @backstage/core-components@0.13.1-next.1 + - @backstage/plugin-search-react@1.6.0-next.2 + - @backstage/integration-react@1.1.13-next.2 + - @backstage/plugin-catalog-react@1.6.0-next.2 + - @backstage/plugin-techdocs-react@1.1.6-next.1 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1 + +## 1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.1-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-catalog-react@1.6.0-next.1 + - @backstage/plugin-search-react@1.6.0-next.1 + - @backstage/integration-react@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.1.6-next.0 + - @backstage/config@1.0.7 + +## 1.6.2-next.0 + +### Patch Changes + +- 863beb49498: Re-add the possibility to have trailing slashes in Techdocs navigation. +- Updated dependencies + - @backstage/plugin-catalog-react@1.6.0-next.0 + - @backstage/integration@1.4.5-next.0 + - @backstage/plugin-search-react@1.6.0-next.0 + - @backstage/integration-react@1.1.13-next.0 + - @backstage/core-components@0.13.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19 + - @backstage/plugin-search-common@1.2.3 + - @backstage/plugin-techdocs-react@1.1.5 + +## 1.6.1 + +### Patch Changes + +- 6c809d1a41c: Minor visual tweaks to adapt to changes in mkdocs-material v9 + +- b2e182cdfa4: Fixes a UI bug in search result item which rendered the item text with incorrect font size and color + +- 847a1eee3da: Change anchor links color in Techdocs content + + With the color (mkdocs supplied) used for anchor links the background and foreground colors do not have a sufficient contrast ratio. Using the link color from theme palette. + +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) + +- 2e493480626: Fix a bug in sub-path navigation due to double addition of a sub-path if one was set up in `app.baseUrl`. + +- e0c6e8b9c3c: Update peer dependencies + +- Updated dependencies + - @backstage/core-components@0.13.0 + - @backstage/plugin-catalog-react@1.5.0 + - @backstage/plugin-search-react@1.5.2 + - @backstage/plugin-techdocs-react@1.1.5 + - @backstage/integration-react@1.1.12 + - @backstage/theme@0.2.19 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/integration@1.4.4 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-search-common@1.2.3 + +## 1.6.1-next.3 + +### Patch Changes + +- 2e493480626: Fix a bug in sub-path navigation due to double addition of a sub-path if one was set up in `app.baseUrl`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.5.0-next.3 + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/core-components@0.13.0-next.3 + - @backstage/config@1.0.7 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/errors@1.1.5 + - @backstage/integration@1.4.4-next.0 + - @backstage/integration-react@1.1.12-next.3 + - @backstage/theme@0.2.19-next.0 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-search-react@1.5.2-next.3 + - @backstage/plugin-techdocs-react@1.1.5-next.3 + +## 1.6.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.6-next.2 + - @backstage/plugin-catalog-react@1.4.1-next.2 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/integration@1.4.4-next.0 + - @backstage/integration-react@1.1.12-next.2 + - @backstage/theme@0.2.19-next.0 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-search-react@1.5.2-next.2 + - @backstage/plugin-techdocs-react@1.1.5-next.2 + +## 1.6.1-next.1 + +### Patch Changes + +- 6c809d1a41c: Minor visual tweaks to adapt to changes in mkdocs-material v9 + +- 847a1eee3da: Change anchor links color in Techdocs content + + With the color (mkdocs supplied) used for anchor links the background and foreground colors do not have a sufficient contrast ratio. Using the link color from theme palette. + +- e0c6e8b9c3c: Update peer dependencies + +- Updated dependencies + - @backstage/core-components@0.12.6-next.1 + - @backstage/integration-react@1.1.12-next.1 + - @backstage/core-plugin-api@1.5.1-next.0 + - @backstage/plugin-techdocs-react@1.1.5-next.1 + - @backstage/plugin-catalog-react@1.4.1-next.1 + - @backstage/integration@1.4.4-next.0 + - @backstage/plugin-search-react@1.5.2-next.1 + - @backstage/theme@0.2.19-next.0 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/plugin-search-common@1.2.3-next.0 + +## 1.6.1-next.0 + +### Patch Changes + +- b2e182cdfa4: Fixes a UI bug in search result item which rendered the item text with incorrect font size and color +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) +- Updated dependencies + - @backstage/core-components@0.12.6-next.0 + - @backstage/plugin-search-react@1.5.2-next.0 + - @backstage/plugin-techdocs-react@1.1.5-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.0 + - @backstage/integration-react@1.1.12-next.0 + - @backstage/core-plugin-api@1.5.0 + - @backstage/config@1.0.7 + - @backstage/integration@1.4.3 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.18 + - @backstage/plugin-search-common@1.2.2 + +## 1.6.0 + +### Minor Changes + +- 3f75b7607ca: Add ability to pass icon as function to have ability to customize it by search item + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 54a1e133b56: Fix bug that caused next and previous links not to work with certain versions of mkdocs-material +- f320c299c67: The HTML tag attributes in the documentation content inserted to shadow DOM is preserved to improve accessibility +- cb8ec97cdeb: Change black & white colors to be theme aware +- c10384a9235: Switch to using `LinkButton` instead of the deprecated `Button` +- 8adfda60ae1: Updated dependency `jss` to `~10.10.0`. +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 238cf657c09: Copy to clipboard now works in a not secure context. +- Updated dependencies + - @backstage/core-components@0.12.5 + - @backstage/plugin-techdocs-react@1.1.4 + - @backstage/plugin-catalog-react@1.4.0 + - @backstage/plugin-search-react@1.5.1 + - @backstage/errors@1.1.5 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-model@1.2.1 + - @backstage/integration-react@1.1.11 + - @backstage/integration@1.4.3 + - @backstage/config@1.0.7 + - @backstage/theme@0.2.18 + - @backstage/plugin-search-common@1.2.2 + +## 1.6.0-next.2 + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- Updated dependencies + - @backstage/core-components@0.12.5-next.2 + - @backstage/plugin-techdocs-react@1.1.4-next.2 + - @backstage/plugin-catalog-react@1.4.0-next.2 + - @backstage/plugin-search-react@1.5.1-next.2 + - @backstage/core-plugin-api@1.5.0-next.2 + - @backstage/integration-react@1.1.11-next.2 + - @backstage/config@1.0.7-next.0 + - @backstage/integration@1.4.3-next.0 + +## 1.6.0-next.1 + +### Patch Changes + +- 54a1e133b56: Fix bug that caused next and previous links not to work with certain versions of mkdocs-material +- cb8ec97cdeb: Change black & white colors to be theme aware +- c10384a9235: Switch to using `LinkButton` instead of the deprecated `Button` +- 8adfda60ae1: Updated dependency `jss` to `~10.10.0`. +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 238cf657c09: Copy to clipboard now works in a not secure context. +- Updated dependencies + - @backstage/core-components@0.12.5-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/plugin-techdocs-react@1.1.4-next.1 + - @backstage/core-plugin-api@1.4.1-next.1 + - @backstage/integration-react@1.1.11-next.1 + - @backstage/integration@1.4.3-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/theme@0.2.18-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.1 + - @backstage/catalog-model@1.2.1-next.1 + - @backstage/plugin-search-common@1.2.2-next.0 + - @backstage/plugin-search-react@1.5.1-next.1 + +## 1.6.0-next.0 + +### Minor Changes + +- 3f75b7607c: Add ability to pass icon as function to have ability to customize it by search item + +### Patch Changes + +- f320c299c6: The HTML tag attributes in the documentation content inserted to shadow DOM is preserved to improve accessibility +- Updated dependencies + - @backstage/plugin-catalog-react@1.4.0-next.0 + - @backstage/core-plugin-api@1.4.1-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.1.4-next.0 + - @backstage/config@1.0.6 + - @backstage/core-components@0.12.5-next.0 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2 + - @backstage/integration-react@1.1.11-next.0 + - @backstage/theme@0.2.17 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-search-react@1.5.1-next.0 + +## 1.5.0 + +### Minor Changes + +- 20840b36b4: Update DocsTable and EntityListDocsTable to accept overrides for Material Table options. +- 0eaa579f89: The `TechDocsSearchResultListItem` component is now a search result extension. This means that when rendered as a child of components that render search extensions, the `result`, `rank`, and `highlight` properties are optional. See the [documentation](https://backstage.io/docs/features/search/how-to-guides#how-to-render-search-results-using-extensions) for more details. + +### Patch Changes + +- c8e09cc383: Fixed bug in Techdocs reader where a techdocs page with a hash in the URL did not always jump to the document anchor. + +- cad5607411: Improve view: remove footer overlay on large screen + +- 66e2aab4c4: `ListItem` wrapper component moved to `SearchResultListItemExtension` for all `*SearchResultListItems` that are exported as extensions. This is to make sure the list only contains list elements. + + Note: If you have implemented a custom result list item, we recommend you to remove the list item wrapper to avoid nested `
  • ` elements. + +- 4660b63947: Create a TechDocs `` addon that allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. + + Here's an example on how to use it in a Backstage app: + + ```diff + import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, + } from '@backstage/plugin-techdocs'; + import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; + +import { LightBox } from '@backstage/plugin-techdocs-module-addons-contrib'; + + const AppRoutes = () => { + + // other plugin routes + }> + + + } + > + + + + + + ; + }; + ``` + +- Updated dependencies + - @backstage/core-components@0.12.4 + - @backstage/catalog-model@1.2.0 + - @backstage/theme@0.2.17 + - @backstage/core-plugin-api@1.4.0 + - @backstage/plugin-catalog-react@1.3.0 + - @backstage/plugin-search-react@1.5.0 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2 + - @backstage/integration-react@1.1.10 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-techdocs-react@1.1.3 + +## 1.5.0-next.2 + +### Patch Changes + +- 66e2aab4c4: `ListItem` wrapper component moved to `SearchResultListItemExtension` for all `*SearchResultListItems` that are exported as extensions. This is to make sure the list only contains list elements. + + Note: If you have implemented a custom result list item, we recommend you to remove the list item wrapper to avoid nested `
  • ` elements. + +- Updated dependencies + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/plugin-search-react@1.5.0-next.1 + - @backstage/core-components@0.12.4-next.1 + - @backstage/config@1.0.6 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2 + - @backstage/integration-react@1.1.10-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-react@1.3.0-next.2 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-techdocs-react@1.1.3-next.2 + +## 1.5.0-next.1 + +### Minor Changes + +- 20840b36b4: Update DocsTable and EntityListDocsTable to accept overrides for Material Table options. +- 0eaa579f89: The `TechDocsSearchResultListItem` component is now a search result extension. This means that when rendered as a child of components that render search extensions, the `result`, `rank`, and `highlight` properties are optional. See the [documentation](https://backstage.io/docs/features/search/how-to-guides#how-to-render-search-results-using-extensions) for more details. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.4-next.0 + - @backstage/plugin-search-react@1.5.0-next.0 + - @backstage/plugin-catalog-react@1.3.0-next.1 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/config@1.0.6 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2 + - @backstage/integration-react@1.1.10-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-techdocs-react@1.1.3-next.1 + +## 1.4.4-next.0 + +### Patch Changes + +- c8e09cc383: Fixed bug in Techdocs reader where a techdocs page with a hash in the URL did not always jump to the document anchor. +- cad5607411: Improve view: remove footer overlay on large screen +- Updated dependencies + - @backstage/plugin-catalog-react@1.3.0-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.1.3-next.0 + - @backstage/integration-react@1.1.9 + +## 1.4.3 + +### Patch Changes + +- a74dd61534: Fix sizing of build log component to render all lines +- 80ce4e8c29: Small updates to some components to ensure theme typography properties are inherited correctly. +- 7115c7389b: Updated dependency `jss` to `~10.9.0`. +- Updated dependencies + - @backstage/catalog-model@1.1.5 + - @backstage/plugin-catalog-react@1.2.4 + - @backstage/core-components@0.12.3 + - @backstage/plugin-search-react@1.4.0 + - @backstage/core-plugin-api@1.3.0 + - @backstage/plugin-techdocs-react@1.1.2 + - @backstage/config@1.0.6 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2 + - @backstage/integration-react@1.1.9 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.2.1 + +## 1.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.4.0-next.2 + - @backstage/core-plugin-api@1.3.0-next.1 + - @backstage/plugin-catalog-react@1.2.4-next.2 + - @backstage/plugin-techdocs-react@1.1.2-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/core-components@0.12.3-next.2 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2-next.0 + - @backstage/integration-react@1.1.9-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.2.1-next.0 + +## 1.4.3-next.1 + +### Patch Changes + +- a74dd61534: Fix sizing of build log component to render all lines +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/core-components@0.12.3-next.1 + - @backstage/core-plugin-api@1.2.1-next.0 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.2-next.0 + - @backstage/integration-react@1.1.9-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-react@1.2.4-next.1 + - @backstage/plugin-search-common@1.2.1-next.0 + - @backstage/plugin-search-react@1.3.2-next.1 + - @backstage/plugin-techdocs-react@1.1.2-next.1 + +## 1.4.3-next.0 + +### Patch Changes + +- 7115c7389b: Updated dependency `jss` to `~10.9.0`. +- Updated dependencies + - @backstage/catalog-model@1.1.5-next.0 + - @backstage/plugin-catalog-react@1.2.4-next.0 + - @backstage/core-components@0.12.3-next.0 + - @backstage/plugin-techdocs-react@1.1.2-next.0 + - @backstage/config@1.0.5 + - @backstage/core-plugin-api@1.2.0 + - @backstage/errors@1.1.4 + - @backstage/integration@1.4.1 + - @backstage/integration-react@1.1.9-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.2.0 + - @backstage/plugin-search-react@1.3.2-next.0 + +## 1.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.2 + - @backstage/integration-react@1.1.8 + - @backstage/plugin-catalog-react@1.2.3 + - @backstage/plugin-search-react@1.3.1 + - @backstage/plugin-techdocs-react@1.1.1 + +## 1.4.1 + +### Patch Changes + +- d3fea4ae0a: Internal fixes to avoid implicit usage of globals +- 2e701b3796: Internal refactor to use `react-router-dom` rather than `react-router`. +- a19cffbeed: Update search links to only have header as linkable text +- 5d3058355d: Add `react/forbid-elements` linter rule for button, suggest Material UI `Button` +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 786f1b1419: Support older versions of react-router +- Updated dependencies + - @backstage/plugin-techdocs-react@1.1.0 + - @backstage/core-plugin-api@1.2.0 + - @backstage/plugin-search-react@1.3.0 + - @backstage/core-components@0.12.1 + - @backstage/errors@1.1.4 + - @backstage/plugin-catalog-react@1.2.2 + - @backstage/integration-react@1.1.7 + - @backstage/integration@1.4.1 + - @backstage/plugin-search-common@1.2.0 + - @backstage/catalog-model@1.1.4 + - @backstage/config@1.0.5 + - @backstage/theme@0.2.16 + +## 1.4.1-next.4 + +### Patch Changes + +- 2e701b3796: Internal refactor to use `react-router-dom` rather than `react-router`. +- Updated dependencies + - @backstage/core-components@0.12.1-next.4 + - @backstage/plugin-catalog-react@1.2.2-next.4 + - @backstage/plugin-search-react@1.3.0-next.4 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/errors@1.1.4-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/integration-react@1.1.7-next.4 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.2.0-next.3 + - @backstage/plugin-techdocs-react@1.0.7-next.4 + +## 1.4.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.1-next.3 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/errors@1.1.4-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/integration-react@1.1.7-next.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-react@1.2.2-next.3 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/plugin-search-react@1.3.0-next.3 + - @backstage/plugin-techdocs-react@1.0.7-next.3 + +## 1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.2.0-next.2 + - @backstage/plugin-search-react@1.3.0-next.2 + - @backstage/core-components@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.2.2-next.2 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/integration-react@1.1.7-next.2 + - @backstage/plugin-techdocs-react@1.0.7-next.2 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/theme@0.2.16 + +## 1.4.1-next.1 + +### Patch Changes + +- d3fea4ae0a: Internal fixes to avoid implicit usage of globals +- a19cffbeed: Update search links to only have header as linkable text +- Updated dependencies + - @backstage/core-components@0.12.1-next.1 + - @backstage/plugin-search-react@1.2.2-next.1 + - @backstage/core-plugin-api@1.1.1-next.1 + - @backstage/plugin-catalog-react@1.2.2-next.1 + - @backstage/integration-react@1.1.7-next.1 + - @backstage/plugin-techdocs-react@1.0.7-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.2-next.1 + +## 1.4.1-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/plugin-techdocs-react@1.0.7-next.0 + - @backstage/core-components@0.12.1-next.0 + - @backstage/core-plugin-api@1.1.1-next.0 + - @backstage/integration-react@1.1.7-next.0 + - @backstage/integration@1.4.1-next.0 + - @backstage/plugin-catalog-react@1.2.2-next.0 + - @backstage/catalog-model@1.1.4-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.2-next.0 + - @backstage/plugin-search-react@1.2.2-next.0 + +## 1.4.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +- 63705e73d9: Hide document description if not provided + +- 847fc588a6: Updated TechDocs header to include label for source code icon and updated label to reflect Kind name + +### Patch Changes + +- 9e4d8e6198: Fix logic bug that broke techdocs-cli-embedded-app +- e92aa15f01: Bumped `canvas` dependency to the latest version, which has better Node.js v18 support. +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- bd2aab4726: An analytics event matching the semantics of the `click` action is now captured when users click links within a TechDocs document. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1 + - @backstage/core-components@0.12.0 + - @backstage/core-plugin-api@1.1.0 + - @backstage/integration@1.4.0 + - @backstage/catalog-model@1.1.3 + - @backstage/plugin-techdocs-react@1.0.6 + - @backstage/integration-react@1.1.6 + - @backstage/plugin-search-react@1.2.1 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1 + +## 1.4.0-next.2 + +### Patch Changes + +- e92aa15f01: Bumped `canvas` dependency to the latest version, which has better Node.js v18 support. +- Updated dependencies + - @backstage/core-components@0.12.0-next.1 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/integration-react@1.1.6-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-react@1.2.1-next.1 + - @backstage/plugin-search-common@1.1.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.1 + - @backstage/plugin-techdocs-react@1.0.6-next.1 + +## 1.4.0-next.1 + +### Patch Changes + +- 9e4d8e6198: Fix logic bug that broke techdocs-cli-embedded-app + +## 1.4.0-next.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +### Patch Changes + +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## 1.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 1.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + +## 1.3.2 + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. +- eadf56bbbf: Bump `git-url-parse` version to `^13.0.0` +- 3f739be9d9: Minor API signatures cleanup +- 763fb81e82: Internal refactor to use more type safe code when dealing with route parameters. +- 7d47def9c4: Removed dependency on `@types/jest`. +- 817f3196f6: Updated the `TechDocsReaderPage` to be compatible with React Router v6 stable. +- 7a95c705fa: Fixed a bug where addons wouldn't render on sub pages when using React Route v6 stable. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- ca8d5a6eae: Use the new `SearchAutocomplete` component in the `TechDocsSearch` component to maintain consistency across search experiences and avoid code duplication. +- 829f14a9b0: Always update the title and sub-title when the location changes on a `TechDocs` reader page. +- e97d616f08: Fixed a bug where scrolling for anchors where the id starts with number didn't work for the current page. +- ef9ab322de: Minor API signatures cleanup +- Updated dependencies + - @backstage/core-components@0.11.1 + - @backstage/core-plugin-api@1.0.6 + - @backstage/plugin-catalog-react@1.1.4 + - @backstage/plugin-search-react@1.1.0 + - @backstage/plugin-techdocs-react@1.0.4 + - @backstage/integration@1.3.1 + - @backstage/catalog-model@1.1.1 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + - @backstage/integration-react@1.1.4 + - @backstage/plugin-search-common@1.0.1 + +## 1.3.2-next.3 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.4-next.2 + - @backstage/catalog-model@1.1.1-next.0 + - @backstage/config@1.0.2-next.0 + - @backstage/core-components@0.11.1-next.3 + - @backstage/core-plugin-api@1.0.6-next.3 + - @backstage/errors@1.1.1-next.0 + - @backstage/integration@1.3.1-next.2 + - @backstage/integration-react@1.1.4-next.2 + - @backstage/plugin-techdocs-react@1.0.4-next.2 + +## 1.3.2-next.2 + +### Patch Changes + +- eadf56bbbf: Bump `git-url-parse` version to `^13.0.0` +- 7a95c705fa: Fixed a bug where addons wouldn't render on sub pages when using React Route v6 stable. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- ca8d5a6eae: Use the new `SearchAutocomplete` component in the `TechDocsSearch` component to maintain consistency across search experiences and avoid code duplication. +- e97d616f08: Fixed a bug where scrolling for anchors where the id starts with number didn't work for the current page. +- Updated dependencies + - @backstage/integration@1.3.1-next.1 + - @backstage/core-components@0.11.1-next.2 + - @backstage/core-plugin-api@1.0.6-next.2 + - @backstage/integration-react@1.1.4-next.1 + - @backstage/plugin-search-react@1.1.0-next.2 + +## 1.3.2-next.1 + +### Patch Changes + +- 817f3196f6: Updated React Router dependencies to be peer dependencies. +- 763fb81e82: Internal refactor to use more type safe code when dealing with route parameters. +- 817f3196f6: Updated the `TechDocsReaderPage` to be compatible with React Router v6 stable. +- Updated dependencies + - @backstage/core-components@0.11.1-next.1 + - @backstage/core-plugin-api@1.0.6-next.1 + - @backstage/plugin-catalog-react@1.1.4-next.1 + - @backstage/plugin-search-react@1.0.2-next.1 + - @backstage/plugin-techdocs-react@1.0.4-next.1 + +## 1.3.2-next.0 + +### Patch Changes + +- 3f739be9d9: Minor API signatures cleanup +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- 829f14a9b0: Always update the title and sub-title when the location changes on a `TechDocs` reader page. +- ef9ab322de: Minor API signatures cleanup +- Updated dependencies + - @backstage/core-plugin-api@1.0.6-next.0 + - @backstage/core-components@0.11.1-next.0 + - @backstage/integration-react@1.1.4-next.0 + - @backstage/integration@1.3.1-next.0 + - @backstage/plugin-catalog-react@1.1.4-next.0 + - @backstage/plugin-search-react@1.0.2-next.0 + - @backstage/plugin-techdocs-react@1.0.4-next.0 + - @backstage/plugin-search-common@1.0.1-next.0 + +## 1.3.1 + +### Patch Changes + +- e924d2d013: Added back reduction in size, this fixes the extremely large TeachDocs headings +- b86ed4d990: Add highlight to active navigation item and navigation parents. +- 7a98c73dc8: Fixed techdocs sidebar layout bug for medium devices. +- 8acb22205c: Scroll techdocs navigation into focus and expand any nested navigation items. +- Updated dependencies + - @backstage/integration@1.3.0 + - @backstage/core-components@0.11.0 + - @backstage/core-plugin-api@1.0.5 + - @backstage/plugin-catalog-react@1.1.3 + - @backstage/plugin-techdocs-react@1.0.3 + - @backstage/integration-react@1.1.3 + - @backstage/plugin-search-react@1.0.1 + +## 1.3.1-next.2 + +### Patch Changes + +- 8acb22205c: Scroll techdocs navigation into focus and expand any nested navigation items. +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.3-next.2 + - @backstage/core-components@0.11.0-next.2 + - @backstage/integration-react@1.1.3-next.1 + - @backstage/plugin-search-react@1.0.1-next.1 + - @backstage/plugin-techdocs-react@1.0.3-next.2 + +## 1.3.1-next.1 + +### Patch Changes + +- b86ed4d990: Add highlight to active navigation item and navigation parents. +- Updated dependencies + - @backstage/core-components@0.10.1-next.1 + - @backstage/integration@1.3.0-next.1 + - @backstage/plugin-techdocs-react@1.0.3-next.1 + - @backstage/plugin-catalog-react@1.1.3-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- 7a98c73dc8: Fixed techdocs sidebar layout bug for medium devices. +- Updated dependencies + - @backstage/integration@1.3.0-next.0 + - @backstage/core-plugin-api@1.0.5-next.0 + - @backstage/integration-react@1.1.3-next.0 + - @backstage/plugin-catalog-react@1.1.3-next.0 + - @backstage/core-components@0.10.1-next.0 + - @backstage/plugin-search-react@1.0.1-next.0 + - @backstage/plugin-techdocs-react@1.0.3-next.0 + +## 1.3.0 + +### Minor Changes + +- ebf3eb1641: Use the same initial filter `owned` for the `TechDocsIndexPage` as for the `CatalogPage`. + + If you prefer to keep the previous behavior, you can change the default for the initial filter + to `all` (or `starred` if you rather prefer that). + + + + In general, with this change you will be able to set props at `TechDocsIndexPage`. + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. + +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. + +- e2d7b76f43: Upgrade git-url-parse to 12.0.0. + + Motivation for upgrade is transitively upgrading parse-url which is vulnerable + to several CVEs detected by Snyk. + + - SNYK-JS-PARSEURL-2935944 + - SNYK-JS-PARSEURL-2935947 + - SNYK-JS-PARSEURL-2936249 + +- 3cbebf710e: Reorder browser tab title in Techdocs pages to have the site name first. + +- 726577958f: Remove the 60% factor from the font size calculation of headers to use the exact size defined in BackstageTheme. + +- 7739141ab2: Fix: When docs are shown in an entity page under the docs tab the sidebars start overlapping with the header and tabs in the page when you scroll the documentation content. + +- Updated dependencies + - @backstage/core-components@0.10.0 + - @backstage/catalog-model@1.1.0 + - @backstage/plugin-techdocs-react@1.0.2 + - @backstage/plugin-search-react@1.0.0 + - @backstage/plugin-search-common@1.0.0 + - @backstage/core-plugin-api@1.0.4 + - @backstage/integration@1.2.2 + - @backstage/integration-react@1.1.2 + - @backstage/plugin-catalog-react@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/errors@1.1.0 + +## 1.2.1-next.3 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.0.4-next.0 + - @backstage/core-components@0.10.0-next.3 + - @backstage/integration-react@1.1.2-next.3 + - @backstage/integration@1.2.2-next.3 + - @backstage/catalog-model@1.1.0-next.3 + - @backstage/plugin-catalog-react@1.1.2-next.3 + - @backstage/plugin-search-react@0.2.2-next.3 + - @backstage/plugin-techdocs-react@1.0.2-next.2 + +## 1.2.1-next.2 + +### Patch Changes + +- e2d7b76f43: Upgrade git-url-parse to 12.0.0. + + Motivation for upgrade is transitively upgrading parse-url which is vulnerable + to several CVEs detected by Snyk. + + - SNYK-JS-PARSEURL-2935944 + - SNYK-JS-PARSEURL-2935947 + - SNYK-JS-PARSEURL-2936249 + +- 7739141ab2: Fix: When docs are shown in an entity page under the docs tab the sidebars start overlapping with the header and tabs in the page when you scroll the documentation content. + +- Updated dependencies + - @backstage/core-components@0.10.0-next.2 + - @backstage/catalog-model@1.1.0-next.2 + - @backstage/plugin-search-react@0.2.2-next.2 + - @backstage/theme@0.2.16-next.1 + - @backstage/integration@1.2.2-next.2 + - @backstage/plugin-catalog-react@1.1.2-next.2 + - @backstage/integration-react@1.1.2-next.2 + - @backstage/plugin-techdocs-react@1.0.2-next.1 + +## 1.2.1-next.1 + +### Patch Changes + +- 726577958f: Remove the 60% factor from the font size calculation of headers to use the exact size defined in BackstageTheme. +- Updated dependencies + - @backstage/core-components@0.9.6-next.1 + - @backstage/catalog-model@1.1.0-next.1 + - @backstage/errors@1.1.0-next.0 + - @backstage/theme@0.2.16-next.0 + - @backstage/integration@1.2.2-next.1 + - @backstage/integration-react@1.1.2-next.1 + - @backstage/plugin-catalog-react@1.1.2-next.1 + - @backstage/plugin-search-common@0.3.6-next.0 + - @backstage/plugin-search-react@0.2.2-next.1 + +## 1.2.1-next.0 + +### Patch Changes + +- 3cbebf710e: Reorder browser tab title in Techdocs pages to have the site name first. +- Updated dependencies + - @backstage/catalog-model@1.1.0-next.0 + - @backstage/core-components@0.9.6-next.0 + - @backstage/plugin-techdocs-react@1.0.2-next.0 + - @backstage/integration@1.2.2-next.0 + - @backstage/plugin-catalog-react@1.1.2-next.0 + - @backstage/integration-react@1.1.2-next.0 + - @backstage/plugin-search-react@0.2.2-next.0 + +## 1.2.0 + +### Minor Changes + +- fe7614ea54: Add an optional icon to the Catalog and TechDocs search results + +### Patch Changes + +- d047d81295: Use entity title as label in `TechDocsReaderPageHeader` if available + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +- bff65e6958: Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()` from `@backstage/core-components`. + +- 915700f64f: In order to simplify analytics on top of the search experience in Backstage, the provided `<*ResultListItem />` component now captures a `discover` analytics event instead of a `click` event. This event includes the result rank as its `value` and, like a click, the URL/path clicked to as its `to` attribute. + +- 881fbd7e8d: Fix `EntityTechdocsContent` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. + +- 17c059dfd0: Restructures reader style transformations to improve code readability: + + - Extracts the style rules to separate files; + - Creates a hook that processes each rule; + - And creates another hook that returns a transformer responsible for injecting them into the head tag of a given element. + +- 3b45ad701f: Packages a set of tweaks to the TechDocs addons rendering process: + + - Prevents displaying sidebars until page styles are loaded and the sidebar position is updated; + - Prevents new sidebar locations from being created every time the reader page is rendered if these locations already exist; + - Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times. + +- 9b94ade898: Use entity title in `TechDocsSearch` placeholder if available. + +- 816f7475ec: Convert `sanitizeDOM` transformer to hook as part of code readability improvements in dom file. + +- 50ff56a80f: Change the `EntityDocsPage` path to be more specific and also add integration tests for `sub-routes` on this page. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1 + - @backstage/plugin-search-common@0.3.5 + - @backstage/plugin-search-react@0.2.1 + - @backstage/core-components@0.9.5 + - @backstage/integration@1.2.1 + - @backstage/core-plugin-api@1.0.3 + - @backstage/integration-react@1.1.1 + - @backstage/catalog-model@1.0.3 + - @backstage/plugin-techdocs-react@1.0.1 + +## 1.1.2-next.1 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. +- bff65e6958: Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()` from `@backstage/core-components`. +- Updated dependencies + - @backstage/core-components@0.9.5-next.1 + - @backstage/core-plugin-api@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.1 + - @backstage/integration@1.2.1-next.1 + - @backstage/catalog-model@1.0.3-next.0 + - @backstage/plugin-catalog-react@1.1.1-next.1 + - @backstage/plugin-search-react@0.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.1 + - @backstage/plugin-search-common@0.3.5-next.0 + +## 1.1.2-next.0 + +### Patch Changes + +- 881fbd7e8d: Fix `EntityTechdocsContent` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. + +- 17c059dfd0: Restructures reader style transformations to improve code readability: + + - Extracts the style rules to separate files; + - Creates a hook that processes each rule; + - And creates another hook that returns a transformer responsible for injecting them into the head tag of a given element. + +- 3b45ad701f: Packages a set of tweaks to the TechDocs addons rendering process: + + - Prevents displaying sidebars until page styles are loaded and the sidebar position is updated; + - Prevents new sidebar locations from being created every time the reader page is rendered if these locations already exist; + - Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times. + +- 816f7475ec: Convert `sanitizeDOM` transformer to hook as part of code readability improvements in dom file. + +- 50ff56a80f: Change the `EntityDocsPage` path to be more specific and also add integration tests for `sub-routes` on this page. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## 1.1.1 + +### Patch Changes + +- 52419be116: Create a menu in the sub header of documentation pages, it is responsible for rendering TechDocs addons that allow users to customize their reading experience. +- a307a14be0: Removed dependency on `@backstage/core-app-api`. +- bed0d64ce9: Fixed bugs that prevented a 404 error from being shown when it should have been. +- 2efee91251: Add a `sub-route` path on the EntityDocs page to fix the blank screen error when navigating using sidebar links. +- 2dcb2c9678: Loading SVGs correctly with `bota` with extended characters +- 52fddad92d: The `TechDocsStorageApi` and its associated ref are now exported by `@backstage/plugin-techdocs-react`. The API interface, ref, and types are now deprecated in `@backstage/plugin-techdocs` and will be removed in a future release. +- 0ad901569f: Hidden exports related to experimental TechDocs reader functionality have been removed and can no longer be imported. In the unlikely event you were using these exports, you can now take advantage of the officially supported and generally available TechDocs Addon framework instead. +- 3a74e203a8: Updated search result components to support rendering content with highlighted matched terms +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/integration@1.2.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-catalog-react@1.1.0 + - @backstage/integration-react@1.1.0 + - @backstage/plugin-techdocs-react@1.0.0 + - @backstage/config@1.0.1 + - @backstage/plugin-search-react@0.2.0 + - @backstage/plugin-search-common@0.3.4 + - @backstage/catalog-model@1.0.2 + +## 1.1.1-next.3 + +### Patch Changes + +- cc8ddd0979: revert dependency `event-source-polyfill` to `1.0.25` +- Updated dependencies + - @backstage/core-components@0.9.4-next.2 + +## 1.1.1-next.2 + +### Patch Changes + +- 52419be116: Create a menu in the sub header of documentation pages, it is responsible for rendering TechDocs addons that allow users to customize their reading experience. +- 1af133f779: Updated dependency `event-source-polyfill` to `1.0.26`. +- 2dcb2c9678: Loading SVGs correctly with `bota` with extended characters +- 3a74e203a8: Updated search result components to support rendering content with highlighted matched terms +- Updated dependencies + - @backstage/core-components@0.9.4-next.1 + - @backstage/plugin-techdocs-react@0.1.1-next.2 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-search-react@0.2.0-next.2 + - @backstage/plugin-search-common@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.1.0-next.2 + - @backstage/catalog-model@1.0.2-next.0 + - @backstage/core-plugin-api@1.0.2-next.1 + - @backstage/integration@1.2.0-next.1 + - @backstage/integration-react@1.1.0-next.2 + +## 1.1.1-next.1 + +### Patch Changes + +- 52fddad92d: The `TechDocsStorageApi` and its associated ref are now exported by `@backstage/plugin-techdocs-react`. The API interface, ref, and types are now deprecated in `@backstage/plugin-techdocs` and will be removed in a future release. +- Updated dependencies + - @backstage/core-components@0.9.4-next.0 + - @backstage/core-plugin-api@1.0.2-next.0 + - @backstage/plugin-catalog-react@1.1.0-next.1 + - @backstage/plugin-search-react@0.2.0-next.1 + - @backstage/plugin-techdocs-react@0.1.1-next.1 + - @backstage/integration-react@1.1.0-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- a307a14be0: Removed dependency on `@backstage/core-app-api`. +- bed0d64ce9: Fixed bugs that prevented a 404 error from being shown when it should have been. +- Updated dependencies + - @backstage/integration@1.2.0-next.0 + - @backstage/plugin-catalog-react@1.1.0-next.0 + - @backstage/integration-react@1.1.0-next.0 + - @backstage/plugin-search-react@0.1.1-next.0 + - @backstage/plugin-techdocs-react@0.1.1-next.0 + +## 1.1.0 + +### Minor Changes + +- ace749b785: TechDocs supports a new, experimental method of customization: addons! + + To customize the standalone TechDocs reader page experience, update your `/packages/app/src/App.tsx` in the following way: + + ```diff + import { TechDocsIndexPage, TechDocsReaderPage } from '@backstage/plugin-techdocs'; + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { SomeAddon } from '@backstage/plugin-some-plugin'; + + // ... + + } /> + } + > + + + + + + + + + // ... + ``` + + To customize the TechDocs reader experience on the Catalog entity page, update your `packages/app/src/components/catalog/EntityPage.tsx` in the following way: + + ```diff + import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { SomeAddon } from '@backstage/plugin-some-plugin'; + + // ... + + + + {overviewContent} + + + + - + + + + + + + + + + + + + + // ... + ``` + + If you do not wish to customize your TechDocs reader experience in this way at this time, no changes are necessary! + +### Patch Changes + +- ab230a433f: imports from `@backstage/plugin-search-react` instead of `@backstage/plugin-search` + +- 7c7919777e: build(deps-dev): bump `@testing-library/react-hooks` from 7.0.2 to 8.0.0 + +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 + +- 230ad0826f: Bump to using `@types/node` v16 + +- f0fb9153b7: Fix broken query selectors on techdocs + +- 9975ff9852: Applied the fix from version 1.0.1 of this package, which is part of the v1.0.2 release of Backstage. + +- 3ba256c389: Fixed a bug preventing custom TechDocs reader page implementations from rendering without being double-wrapped in the `` component. + +- fe53fe97d7: Fix permalink scrolling for anchors where the id starts with a number. + +- 0152c0de22: Some documentation layout tweaks: + + - drawer toggle margins + - code block margins + - sidebar drawer width + - inner content width + - footer link width + - sidebar table of contents scroll + +- 3ba256c389: Fixed a bug that caused addons in the `Subheader` location to break the default TechDocs reader page layout. + +- Updated dependencies + - @backstage/integration@1.1.0 + - @backstage/plugin-catalog-react@1.0.1 + - @backstage/catalog-model@1.0.1 + - @backstage/core-app-api@1.0.1 + - @backstage/core-components@0.9.3 + - @backstage/core-plugin-api@1.0.1 + - @backstage/plugin-search-react@0.1.0 + - @backstage/plugin-techdocs-react@0.1.0 + - @backstage/integration-react@1.0.1 + +## 1.1.0-next.3 + +### Minor Changes + +- ace749b785: TechDocs supports a new, experimental method of customization: addons! + + To customize the standalone TechDocs reader page experience, update your `/packages/app/src/App.tsx` in the following way: + + ```diff + import { TechDocsIndexPage, TechDocsReaderPage } from '@backstage/plugin-techdocs'; + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { SomeAddon } from '@backstage/plugin-some-plugin'; + + // ... + + } /> + } + > + + + + + + + + + // ... + ``` + + To customize the TechDocs reader experience on the Catalog entity page, update your `packages/app/src/components/catalog/EntityPage.tsx` in the following way: + + ```diff + import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { SomeAddon } from '@backstage/plugin-some-plugin'; + + // ... + + + + {overviewContent} + + + + - + + + + + + + + + + + + + + // ... + ``` + + If you do not wish to customize your TechDocs reader experience in this way at this time, no changes are necessary! + +### Patch Changes + +- ab230a433f: imports from `@backstage/plugin-search-react` instead of `@backstage/plugin-search` +- 24254fd433: build(deps): bump `@testing-library/user-event` from 13.5.0 to 14.0.0 +- 230ad0826f: Bump to using `@types/node` v16 +- Updated dependencies + - @backstage/core-app-api@1.0.1-next.1 + - @backstage/core-components@0.9.3-next.2 + - @backstage/core-plugin-api@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.2 + - @backstage/plugin-catalog-react@1.0.1-next.3 + - @backstage/plugin-search-react@0.1.0-next.0 + - @backstage/integration@1.1.0-next.2 + - @backstage/plugin-techdocs-react@0.1.0-next.0 + +## 1.0.1-next.2 + +### Patch Changes + +- f0fb9153b7: Fix broken query selectors on techdocs +- 9975ff9852: Applied the fix from version 1.0.1 of this package, which is part of the v1.0.2 release of Backstage. +- Updated dependencies + - @backstage/core-components@0.9.3-next.1 + - @backstage/plugin-catalog-react@1.0.1-next.2 + - @backstage/catalog-model@1.0.1-next.1 + +## 1.0.1 + +### Patch Changes + +- Pin the `event-source-polyfill` dependency to version 1.0.25 + +## 1.0.1-next.1 + +### Patch Changes + +- 0152c0de22: Some documentation layout tweaks: + + - drawer toggle margins + - code block margins + - sidebar drawer width + - inner content width + - footer link width + - sidebar table of contents scroll + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + +## 1.0.1-next.0 + +### Patch Changes + +- fe53fe97d7: Fix permalink scrolling for anchors where the id starts with a number. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search@0.7.5-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/integration-react@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 700d93ff41: Removed deprecated exports, including: + + - deprecated `DocsResultListItem` is now deleted and fully replaced with `TechDocsSearchResultListItem` + - deprecated `TechDocsPage` is now deleted and fully replaced with `TechDocsReaderPage` + - deprecated `TechDocsPageHeader` is now deleted and fully replaced with `TechDocsReaderPageHeader` + - deprecated `TechDocsPageHeaderProps` is now deleted and fully replaced with `TechDocsReaderPageHeaderProps` + - deprecated `TechDocsPageRenderFunction` is now deleted and fully replaced with `TechDocsReaderPageRenderFunction` + - deprecated config `techdocs.requestUrl` is now deleted and fully replaced with the discoveryApi + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- c689d7a94c: Switched to using `CatalogFilterLayout` from `@backstage/plugin-catalog-react`. +- f24ef7864e: Minor typo fixes +- 06af9e8d17: Long sidebars will no longer overflow the footer and will properly show a scrollbar when needed. +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-search@0.7.4 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + +## 0.15.1 + +### Patch Changes + +- 7a1dbe6ce9: The panels of `TechDocsCustomHome` now use the `useEntityOwnership` hook to resolve ownership when the `'ownedByUser'` filter predicate is used. +- Updated dependencies + - @backstage/plugin-catalog@0.10.0 + - @backstage/plugin-catalog-react@0.9.0 + - @backstage/core-components@0.9.1 + - @backstage/catalog-model@0.13.0 + - @backstage/plugin-search@0.7.3 + - @backstage/integration-react@0.1.25 + +## 0.15.1-next.0 + +### Patch Changes + +- 7a1dbe6ce9: The panels of `TechDocsCustomHome` now use the `useEntityOwnership` hook to resolve ownership when the `'ownedByUser'` filter predicate is used. +- Updated dependencies + - @backstage/plugin-catalog@0.10.0-next.0 + - @backstage/plugin-catalog-react@0.9.0-next.0 + - @backstage/core-components@0.9.1-next.0 + - @backstage/catalog-model@0.13.0-next.0 + - @backstage/plugin-search@0.7.3-next.0 + - @backstage/integration-react@0.1.25-next.0 + +## 0.15.0 + +### Minor Changes + +- ee3d6c6f10: **BREAKING:** + Table column utilities `createNameColumn`, `createOwnerColumn`, `createTypeColumn` as well as actions utilities `createCopyDocsUrlAction` and `createStarEntityAction` are no longer directly exported. Instead accessible through DocsTable and EntityListDocsTable. + + Use as following: + + ```tsx + DocsTable.columns.createNameColumn(); + DocsTable.columns.createOwnerColumn(); + DocsTable.columns.createTypeColumn(); + + DocsTable.actions.createCopyDocsUrlAction(); + DocsTable.actions.createStarEntityAction(); + ``` + + - Renamed `DocsResultListItem` to `TechDocsSearchResultListItem`, leaving the old name in place as a deprecations. + + - Renamed `TechDocsPage` to `TechDocsReaderPage`, leaving the old name in place as a deprecations. + + - Renamed `TechDocsPageRenderFunction` to `TechDocsPageRenderFunction`, leaving the old name in place as a deprecations. + + - Renamed `TechDocsPageHeader` to `TechDocsReaderPageHeader`, leaving the old name in place as a deprecations. + + - `LegacyTechDocsHome` marked as deprecated and will be deleted in next release, use `TechDocsCustomHome` instead. + + - `LegacyTechDocsPage` marked as deprecated and will be deleted in next release, use `TechDocsReaderPage` instead. + +### Patch Changes + +- 64b430f80d: chore(deps): bump `react-text-truncate` from 0.17.0 to 0.18.0 +- 899f196af5: Use `getEntityByRef` instead of `getEntityByName` in the catalog client +- f41a293231: - **DEPRECATION**: Deprecated `formatEntityRefTitle` in favor of the new `humanizeEntityRef` method instead. Please migrate to using the new method instead. +- c5fda066b1: Collapse techdocs sidebar on small devices +- f590d1681b: Removed usage of deprecated favorite utility methods. +- 5b0f9a75fa: Remove copyright from old footer in documentation generated with previous version of `mkdocs-techdocs-plugin` (`v0.2.2`). +- 0c3ba547a6: Show feedback when copying code snippet to clipboard. +- 0ca964ee0e: Fixed a bug that could cause searches in the in-context TechDocs search bar to show results from a different TechDocs site. +- 36aa63022b: Use `CompoundEntityRef` instead of `EntityName`, and `getCompoundEntityRef` instead of `getEntityName`, from `@backstage/catalog-model`. +- Updated dependencies + - @backstage/catalog-model@0.12.0 + - @backstage/core-components@0.9.0 + - @backstage/plugin-search@0.7.2 + - @backstage/plugin-catalog@0.9.1 + - @backstage/plugin-catalog-react@0.8.0 + - @backstage/integration@0.8.0 + - @backstage/core-plugin-api@0.8.0 + - @backstage/integration-react@0.1.24 + +## 0.14.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. +- 4faae902eb: Adjust the Tech Docs page theme as a side effect of the `mkdocs-material` theme update. + + If you use the `spofify/techdocs` image to build your documentation, make sure you use version `spotify/techdocs:v0.3.7`. + + **Breaking**: The `PyMdown` extensions have also been updated and some syntax may have changed, so it is recommended that you check the extension's documentation if something stops working. + For example, the syntax of tags below was deprecated in `PyMdown` extensions `v.7.0` and in `v.8.0.0` it has been removed. This means that the old syntax specified below no longer works. + + ````markdown + ```markdown tab="tab" + This is some markdown + ``` + + ```markdown tab="tab 2" + This is some markdown in tab 2 + ``` + ```` + +### Patch Changes + +- 3bbb4d98c6: Changed to use from createApp +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- b776ce5aab: Replaced use of deprecated `useEntityListProvider` hook with `useEntityList`. +- d4f67fa728: Removed import of deprecated hook. +- 45e1706328: Continuation of [#9569](https://github.com/backstage/backstage/pull/9569), fix Tech Docs Reader search position to be the same width as content. +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/plugin-search@0.7.1 + - @backstage/integration-react@0.1.23 + +## 0.13.4 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 6553985cd4: Match text size of admonitions to main content text size. +- 9df7b43e1a: Improve overall appearance of highlighted code in docs. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-search@0.7.0 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + +## 0.13.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.8 + - @backstage/plugin-search@0.6.2 + - @backstage/plugin-catalog-react@0.6.14 + - @backstage/plugin-catalog@0.7.12 + - @backstage/integration-react@0.1.21 + +## 0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.8-next.0 + - @backstage/plugin-search@0.6.2-next.0 + - @backstage/plugin-catalog-react@0.6.14-next.0 + - @backstage/integration-react@0.1.21-next.0 + - @backstage/plugin-catalog@0.7.12-next.0 + +## 0.13.2 + +### Patch Changes + +- 742434a6ba: Fixed a bug where links to files within a TechDocs site that use the `download` attribute would result in a 404 in cases where the TechDocs backend and Backstage frontend application are on the same host. +- 359c31e31d: Added support for documentation using the raw `` tag to point to relative resources like audio or video files. +- 18317a08db: Fixed a bug where copy-to-clipboard buttons were appended to unintended elements. +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/integration-react@0.1.20 + - @backstage/plugin-catalog@0.7.11 + - @backstage/plugin-search@0.6.1 + +## 0.13.2-next.1 + +### Patch Changes + +- 742434a6ba: Fixed a bug where links to files within a TechDocs site that use the `download` attribute would result in a 404 in cases where the TechDocs backend and Backstage frontend application are on the same host. +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-catalog@0.7.11-next.1 + +## 0.13.2-next.0 + +### Patch Changes + +- 359c31e31d: Added support for documentation using the raw `` tag to point to relative resources like audio or video files. +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + - @backstage/plugin-search@0.6.1-next.0 + +## 0.13.1 + +### Patch Changes + +- bdc53553eb: chore(deps): bump `react-text-truncate` from 0.16.0 to 0.17.0 +- a64f99f734: Code snippets now include a "copy to clipboard" button. +- Updated dependencies + - @backstage/core-components@0.8.6 + - @backstage/plugin-search@0.6.0 + - @backstage/plugin-catalog@0.7.10 + +## 0.13.0 + +### Minor Changes + +- aecfe4f403: Make `TechDocsClient` and `TechDocsStorageClient` use the `FetchApi`. You now + need to pass in an instance of that API when constructing the client, if you + create a custom instance in your app. + + If you are replacing the factory: + + ```diff + +import { fetchApiRef } from '@backstage/core-plugin-api'; + + createApiFactory({ + api: techdocsStorageApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + + fetchApi: fetchApiRef, + }, + factory: ({ + configApi, + discoveryApi, + identityApi, + + fetchApi, + }) => + new TechDocsStorageClient({ + configApi, + discoveryApi, + identityApi, + + fetchApi, + }), + }), + createApiFactory({ + api: techdocsApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + - identityApi: identityApiRef, + + fetchApi: fetchApiRef, + }, + factory: ({ + configApi, + discoveryApi, + - identityApi, + + fetchApi, + }) => + new TechDocsClient({ + configApi, + discoveryApi, + - identityApi, + + fetchApi, + }), + }), + ``` + + If instantiating directly: + + ```diff + +import { fetchApiRef } from '@backstage/core-plugin-api'; + + +const fetchApi = useApi(fetchApiRef); + const storageClient = new TechDocsStorageClient({ + configApi, + discoveryApi, + identityApi, + + fetchApi, + }); + const techdocsClient = new TechDocsClient({ + configApi, + discoveryApi, + - identityApi, + + fetchApi, + }), + ``` + +### Patch Changes + +- 51fbedc445: Migrated usage of deprecated `IdentityApi` methods. +- 29710c91c2: use lighter color for block quotes and horizontal rulers +- Updated dependencies + - @backstage/core-components@0.8.5 + - @backstage/integration@0.7.2 + - @backstage/plugin-search@0.5.6 + - @backstage/core-plugin-api@0.6.0 + - @backstage/plugin-catalog@0.7.9 + - @backstage/plugin-catalog-react@0.6.12 + - @backstage/config@0.1.13 + - @backstage/catalog-model@0.9.10 + - @backstage/integration-react@0.1.19 + +## 0.12.15-next.0 + +### Patch Changes + +- 51fbedc445: Migrated usage of deprecated `IdentityApi` methods. +- 29710c91c2: use lighter color for block quotes and horizontal rulers +- Updated dependencies + - @backstage/core-components@0.8.5-next.0 + - @backstage/core-plugin-api@0.6.0-next.0 + - @backstage/plugin-catalog@0.7.9-next.0 + - @backstage/config@0.1.13-next.0 + - @backstage/plugin-catalog-react@0.6.12-next.0 + - @backstage/plugin-search@0.5.6-next.0 + - @backstage/catalog-model@0.9.10-next.0 + - @backstage/integration-react@0.1.19-next.0 + - @backstage/integration@0.7.2-next.0 + +## 0.12.14 + +### Patch Changes + +- 5333451def: Cleaned up API exports +- 1628ca3f49: Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/integration@0.7.1 + - @backstage/core-components@0.8.4 + - @backstage/core-plugin-api@0.5.0 + - @backstage/plugin-catalog-react@0.6.11 + - @backstage/errors@0.2.0 + - @backstage/catalog-model@0.9.9 + - @backstage/integration-react@0.1.18 + - @backstage/plugin-catalog@0.7.8 + - @backstage/plugin-search@0.5.5 + +## 0.12.13 + +### Patch Changes + +- fe9de6c25b: Adds support for opening internal Techdocs links in a new tab with CTRL+Click or CMD+Click +- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead. +- e0271456d8: Updated Techdocs footer navigation to dynamically resize to the width of the dom, resolving an issue where a pinned sidebar causes navigation to go off of the screen +- Updated dependencies + - @backstage/plugin-search@0.5.4 + - @backstage/core-plugin-api@0.4.1 + - @backstage/plugin-catalog-react@0.6.10 + - @backstage/core-components@0.8.3 + - @backstage/plugin-catalog@0.7.7 + +## 0.12.12 + +### Patch Changes + +- aa8f764a3e: Add the techdocs.sanitizer.allowedIframeHosts config. + This config allows all iframes which have the host of the attribute src in the 'allowedIframehosts' list to be displayed in the documentation. +- Updated dependencies + - @backstage/plugin-search@0.5.3 + - @backstage/plugin-catalog@0.7.6 + - @backstage/plugin-catalog-react@0.6.9 + - @backstage/integration@0.7.0 + - @backstage/integration-react@0.1.17 + +## 0.12.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search@0.5.2 + - @backstage/core-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@0.6.8 + - @backstage/core-components@0.8.2 + - @backstage/plugin-catalog@0.7.5 + - @backstage/integration-react@0.1.16 + +## 0.12.10 + +### Patch Changes + +- e7cce2b603: Fix issue where assets weren't being fetched from the correct URL path for doc URLs without trailing slashes +- Updated dependencies + - @backstage/core-plugin-api@0.3.1 + - @backstage/core-components@0.8.1 + - @backstage/catalog-model@0.9.8 + - @backstage/plugin-catalog-react@0.6.7 + +## 0.12.9 + +### Patch Changes + +- cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used. +- d90dad84b0: Switch to using `LogViewer` component from `@backstage/core-components` to display build logs. +- 3421826ca8: The problem of lowercase entity triplets which causes docs to not load on entity page is fixed. +- Updated dependencies + - @backstage/core-components@0.8.0 + - @backstage/core-plugin-api@0.3.0 + - @backstage/plugin-catalog@0.7.4 + - @backstage/integration-react@0.1.15 + - @backstage/plugin-catalog-react@0.6.5 + - @backstage/plugin-search@0.5.1 + +## 0.12.8 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.10 + - @backstage/core-components@0.7.6 + - @backstage/theme@0.2.14 + - @backstage/core-plugin-api@0.2.2 + - @backstage/plugin-search@0.5.0 + +## 0.12.7 + +### Patch Changes + +- bab752e2b3: Change default port of backend from 7000 to 7007. + + This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start. + + You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values: + + backend: + listen: 0.0.0.0:7123 + baseUrl: http://localhost:7123 + + More information can be found here: + +- Updated dependencies + - @backstage/errors@0.1.5 + - @backstage/core-plugin-api@0.2.1 + - @backstage/core-components@0.7.5 + +## 0.12.6 + +### Patch Changes + +- a125278b81: Refactor out the deprecated path and icon from RouteRefs +- c1858c4cf9: Fixed entity triplet case handling for certain locales. +- f7703981a9: Use a better checkbox rendering in a task list. +- e266687580: Updates reader component used to display techdocs documentation. A previous change made this component not usable out of a page which don't have entityRef in url parameters. Reader component EntityRef parameter is now used instead of url parameters. Techdocs documentation component can now be used in our custom pages. +- Updated dependencies + - @backstage/plugin-catalog@0.7.3 + - @backstage/catalog-model@0.9.7 + - @backstage/plugin-catalog-react@0.6.4 + - @backstage/plugin-search@0.4.18 + - @backstage/core-components@0.7.4 + - @backstage/core-plugin-api@0.2.0 + - @backstage/integration-react@0.1.14 + +## 0.12.5 + +### Patch Changes + +- fe5738fe1c: Lazy load `LazyLog` as it is rarely used. +- 53c9ad7e04: Update font weight for headings in TechDocs +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/plugin-search@0.4.17 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + +## 0.12.4 + +### Patch Changes + +- a9a8c6f7c5: Reader will now scroll to the top of the page when navigating between pages +- 106a5dc3ad: Restore original casing for `kind`, `namespace` and `name` in `DefaultTechDocsCollator`. +- Updated dependencies + - @backstage/config@0.1.11 + - @backstage/theme@0.2.12 + - @backstage/errors@0.1.4 + - @backstage/integration@0.6.9 + - @backstage/core-components@0.7.2 + - @backstage/integration-react@0.1.13 + - @backstage/plugin-catalog-react@0.6.2 + - @backstage/catalog-model@0.9.6 + - @backstage/plugin-search@0.4.16 + - @backstage/core-plugin-api@0.1.12 + +## 0.12.3 + +### Patch Changes + +- ba5b75ed2f: Add `` as an alternative to `` that + shows a grid of card instead of table. + + Extend `` to display the entity title of the entity instead of the + name if available. + +- 177401b571: Display entity title (if defined) in titles of TechDocs search results + +- cdf8ca6111: Only replace the shadow dom if the content is changed to avoid a flickering UI. + +- Updated dependencies + - @backstage/core-components@0.7.1 + - @backstage/errors@0.1.3 + - @backstage/core-plugin-api@0.1.11 + - @backstage/plugin-catalog@0.7.2 + - @backstage/plugin-catalog-react@0.6.1 + - @backstage/catalog-model@0.9.5 + +## 0.12.2 + +### Patch Changes + +- 76fef740fe: Refactored `` component internals to support future extensibility. +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/plugin-catalog@0.7.1 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/plugin-search@0.4.15 + - @backstage/integration-react@0.1.12 + +## 0.12.1 + +### Patch Changes + +- 81a41ec249: Added a `name` key to all extensions in order to improve Analytics API metadata. +- Updated dependencies + - @backstage/core-components@0.6.1 + - @backstage/core-plugin-api@0.1.10 + - @backstage/plugin-catalog@0.7.0 + - @backstage/plugin-catalog-react@0.5.2 + - @backstage/catalog-model@0.9.4 + - @backstage/integration@0.6.7 + - @backstage/plugin-search@0.4.14 + +## 0.12.0 + +### Minor Changes + +- 82bb0842a3: Adds support for being able to customize and compose your TechDocs reader page in the App. + + You can likely upgrade to this version without issue. If, however, you have + imported the `` component in your custom code, the name of a property + has changed. You will need to make the following change anywhere you use it: + + ```diff + - + + + ``` + +### Patch Changes + +- 79ebee7a6b: Add "data-testid" for e2e tests and fix techdocs entity not found error. +- 3df2e8532b: Fixed the URL for the "Click to copy documentation link to clipboard" action +- 0a8bec0877: Added a check for the TechDocs annotation on the entity +- Updated dependencies + - @backstage/integration@0.6.6 + - @backstage/core-plugin-api@0.1.9 + - @backstage/core-components@0.6.0 + - @backstage/integration-react@0.1.11 + - @backstage/plugin-catalog@0.6.17 + - @backstage/plugin-catalog-react@0.5.1 + - @backstage/plugin-search@0.4.13 + +## 0.11.3 + +### Patch Changes + +- be13dfe61a: Make techdocs context search bar width adjust on smaller screens. +- Updated dependencies + - @backstage/core-components@0.5.0 + - @backstage/integration@0.6.5 + - @backstage/plugin-catalog@0.6.16 + - @backstage/plugin-catalog-react@0.5.0 + - @backstage/catalog-model@0.9.3 + - @backstage/config@0.1.10 + - @backstage/integration-react@0.1.10 + - @backstage/plugin-search@0.4.12 + +## 0.11.2 + +### Patch Changes + +- 1d346ba903: Modify TechDocsCollator to be aware of new TechDocs URL pattern. Modify tech docs in context search to use correct casing when creating initial filter. +- 9f1362dcc1: Upgrade `@material-ui/lab` to `4.0.0-alpha.57`. +- 96fef17a18: Upgrade git-parse-url to v11.6.0 +- Updated dependencies + - @backstage/core-components@0.4.2 + - @backstage/integration@0.6.4 + - @backstage/integration-react@0.1.9 + - @backstage/plugin-catalog@0.6.15 + - @backstage/plugin-catalog-react@0.4.6 + - @backstage/plugin-search@0.4.11 + - @backstage/core-plugin-api@0.1.8 + +## 0.11.1 + +### Patch Changes + +- 30ed662a3: Adding in-context search to TechDocs Reader component. Using existing search-backend to query for indexed search results scoped into a specific entity's techdocs. Needs TechDocsCollator enabled on the backend to work. + + Adding extra information to indexed tech docs documents for search. + +- 434dfc5d4: Display [metadata.title](https://backstage.io/docs/features/software-catalog/descriptor-format#title-optional) for components on the TechDocs homepage, if defined; otherwise fall back to `metadata.name` as displayed before. + +- Updated dependencies + - @backstage/plugin-catalog-react@0.4.5 + - @backstage/integration@0.6.3 + - @backstage/core-components@0.4.0 + - @backstage/plugin-catalog@0.6.14 + - @backstage/plugin-search@0.4.9 + - @backstage/catalog-model@0.9.1 + - @backstage/integration-react@0.1.8 + +## 0.11.0 + +### Minor Changes + +- c772d9a84: TechDocs sites can now be accessed using paths containing entity triplets of + any case (e.g. `/docs/namespace/KIND/name` or `/docs/namespace/kind/name`). + + If you do not use an external storage provider for serving TechDocs, this is a + transparent change and no action is required from you. + + If you _do_ use an external storage provider for serving TechDocs (one of\* GCS, + AWS S3, or Azure Blob Storage), you must run a migration command against your + storage provider before updating. + + [A migration guide is available here](https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-alpha-to-beta). + + - (\*) We're seeking help from the community to bring OpenStack Swift support + [to feature parity](https://github.com/backstage/backstage/issues/6763) with the above. + +- 787bc0826: The TechDocs plugin has completed the migration to the Composability API. In + order to update to this version, please ensure you've made all necessary + changes to your `App.tsx` file as outlined in the [create-app changelog][cacl]. + + [cacl]: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md + +### Patch Changes + +- 90c68a2ca: Fix Techdocs feedback icon link for GitHub URLs +- Updated dependencies + - @backstage/plugin-catalog@0.6.13 + - @backstage/plugin-catalog-react@0.4.4 + - @backstage/core-components@0.3.3 + - @backstage/integration@0.6.2 + - @backstage/config@0.1.8 + +## 0.10.4 + +### Patch Changes + +- a440d3b38: Expose a new composable `TechDocsIndexPage` and a `DefaultTechDocsHome` with support for starring docs and filtering on owned, starred, owner, and tags. + + You can migrate to the new UI view by making the following changes in your `App.tsx`: + + ```diff + - } /> + + }> + + + + + + } + + /> + ``` + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. + +- 8a3e46591: Switch `EventSource` implementation with header support from a Node.js API-based one to an XHR-based one. + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog@0.6.11 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + +## 0.10.3 + +### Patch Changes + +- 260c053b9: Fix All Material UI Warnings +- db58cf06c: Avoid sanitize safe links in the header of document pages. +- 1d65bd490: Fix Techdocs feedback icon link for GitLab URLs with subgroup(s) in path +- Updated dependencies + - @backstage/core-components@0.3.0 + - @backstage/config@0.1.6 + - @backstage/core-plugin-api@0.1.5 + - @backstage/integration@0.5.9 + - @backstage/integration-react@0.1.6 + - @backstage/plugin-catalog-react@0.4.1 + +## 0.10.2 + +### Patch Changes + +- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`. + - Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core` + - This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components. + - `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange` + - `onChangePage` has been renamed to `onPageChange` + - Migration guide is here: +- 11c370af2: Optimize load times by only fetching entities with the `backstage.io/techdocs-ref` annotation +- 2b1ac002d: TechDocs now uses a "safe by default" sanitization library, rather than relying on its own, hard-coded list of allowable tags and attributes. +- Updated dependencies + - @backstage/core-components@0.2.0 + - @backstage/plugin-catalog-react@0.4.0 + - @backstage/core-plugin-api@0.1.4 + - @backstage/integration-react@0.1.5 + - @backstage/theme@0.2.9 + +## 0.10.1 + +### Patch Changes + +- 9266b80ab: Add search list item to display tech docs search results + +- 03bf17e9b: Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + + To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + + ```diff + - + + + ``` + +- 378cc6a54: Only update the `path` when the content is updated. + If content and path are updated independently, the frontend rendering is triggered twice on each navigation: Once for the `path` change (with the old content) and once for the new content. + This might result in a flickering rendering that is caused by the async frontend preprocessing, and the fact that replacing the shadow dom content is expensive. + +- 214e7c52d: Refactor the techdocs transformers to return `Promise`s and await all transformations. + +- e35b13afa: Handle error responses in `getTechDocsMetadata` and `getEntityMetadata` such that `` doesn't throw errors. + +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/plugin-catalog-react@0.3.1 + +## 0.10.0 + +### Minor Changes + +- 94a54dd47: Added a `migrateDocsCase()` method to TechDocs publishers, along with + implementations for AWS, Azure, and GCS. + + This change is in support of a future update to TechDocs that will allow for + case-insensitive entity triplet URL access to documentation pages which will + require a migration of existing documentation objects in external storage + solutions. + + See [#4367](https://github.com/backstage/backstage/issues/4367) for details. + +### Patch Changes + +- 537c37b0f: Fix displaying owned documents list by fetching associated entity relations +- 136a91974: Show a "Refresh" button to if the content is stale. + This removes the need to do a full page-reload to display more recent TechDocs content. +- f1200f44c: Rewrite the `/sync/:namespace/:kind/:name` endpoint to support an event-stream as response. + This change allows the sync process to take longer than a normal HTTP timeout. + The stream also emits log events, so the caller can follow the build process in the frontend. +- 3af126cdd: Provide a Drawer component to follow a running build. + This can be used to debug the rendering and get build logs in case an error occurs. +- 2a4a3b32d: Techdocs: fix sidebars not adjusting position automatically +- Updated dependencies + - @backstage/plugin-catalog-react@0.3.0 + +## 0.9.9 + +### Patch Changes + +- 0172d3424: Fixed bug preventing scroll bar from showing up on code blocks in a TechDocs site. +- Updated dependencies + - @backstage/integration@0.5.8 + - @backstage/core-components@0.1.5 + - @backstage/catalog-model@0.9.0 + - @backstage/plugin-catalog-react@0.2.6 + +## 0.9.8 + +### Patch Changes + +- 99a2873c7: Include cookies when making fetch requests for SVG from techdocs plugin +- a444c7431: Filter fetched entity fields to optimize loading techdocs list +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + +## 0.9.7 + +### Patch Changes + +- aefd54da6: Fix the overlapping between the sidebar and the tabs navigation when enabled in mkdocs (features: navigation.tabs) +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- 1dfec7a2a: Refactor the implicit logic from `` into an explicit state machine. This resolves some state synchronization issues when content is refreshed or rebuilt in the backend. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + +## 0.9.6 + +### Patch Changes + +- 938aee2fb: Fix the link to the documentation page when no owned documents are displayed +- 2e1fbe203: Do not add trailing slash for .html pages during doc links rewriting +- 9b57fda8b: Fixes a bug that could prevent some externally hosted images (like icons or + build badges) from rendering within TechDocs documentation. +- 667656c8b: Adding support for user owned document filter for TechDocs custom Homepage +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.3 + - @backstage/catalog-model@0.8.3 + - @backstage/core@0.7.13 + +## 0.9.5 + +### Patch Changes + +- aad98c544: Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] +- Updated dependencies [1cf1d351f] +- Updated dependencies [deaba2e13] +- Updated dependencies [8e919a6f8] + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + - @backstage/core@0.7.12 + - @backstage/plugin-catalog-react@0.2.1 + +## 0.9.4 + +### Patch Changes + +- 4ea9df9d3: Set admonition font size to 1rem in TechDocs to align with the rest of the document's font sizes. + Fixes #5448 and #5541. +- bf805b467: Fixes #5529, a bug that prevented TechDocs from rendering pages containing malformed links. +- 203ce6f6f: TechDocs now respects the `download` attribute on anchor tags in generated + markup, allowing documentation authors to bundle downloadable files with their + documentation. +- Updated dependencies [0fd4ea443] +- Updated dependencies [add62a455] +- Updated dependencies [cc592248b] +- Updated dependencies [17c497b81] +- Updated dependencies [704875e26] + - @backstage/integration@0.5.4 + - @backstage/catalog-model@0.8.0 + - @backstage/core@0.7.11 + - @backstage/plugin-catalog-react@0.2.0 + +## 0.9.3 + +### Patch Changes + +- 65e6c4541: Remove circular dependencies +- a62cfe068: Bug fix on sidebar position when Tab-Bar is enabled +- 35e091604: Handle URLs with a `#hash` correctly when rewriting link URLs. +- Updated dependencies [f7f7783a3] +- Updated dependencies [65e6c4541] +- Updated dependencies [68fdbf014] +- Updated dependencies [5da6a561d] + - @backstage/catalog-model@0.7.10 + - @backstage/core@0.7.10 + - @backstage/integration@0.5.3 + +## 0.9.2 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + +## 0.9.1 + +### Patch Changes + +- 2e05277e0: Fix navigation in a page using the table of contents. +- 4075c6367: Make git config optional for techdocs feedback links +- Updated dependencies [38ca05168] +- Updated dependencies [f65adcde7] +- Updated dependencies [81c54d1f2] +- Updated dependencies [80888659b] +- Updated dependencies [7b8272fb7] +- Updated dependencies [d8b81fd28] + - @backstage/integration@0.5.2 + - @backstage/core@0.7.8 + - @backstage/plugin-catalog-react@0.1.5 + - @backstage/theme@0.2.7 + - @backstage/catalog-model@0.7.8 + - @backstage/config@0.1.5 + +## 0.9.0 + +### Minor Changes + +- 21fddf452: Make `techdocsStorageApiRef` and `techdocsApiRef` use interfaces instead of the + actual implementation classes. + + This renames the classes `TechDocsApi` to `TechDocsClient` and `TechDocsStorageApi` + to `TechDocsStorageClient` and renames the interfaces `TechDocs` to `TechDocsApi` + and `TechDocsStorage` to `TechDocsStorageApi` to comply the pattern elsewhere in + the project. This also fixes the types returned by some methods on those + interfaces. + +### Patch Changes + +- 6fbd7beca: Use `EntityRefLink` in header and use relations to reference the owner of the + document. +- 15cbe6815: Fix TechDocs landing page table wrong copied link +- 39bdaa004: Add customization and exportable components for TechDocs landing page +- cb8c848a3: Disable color transitions on links to avoid issues in dark mode. +- 17915e29b: Rework state management to avoid rendering multiple while navigating between pages. +- Updated dependencies [9afcac5af] +- Updated dependencies [e0c9ed759] +- Updated dependencies [6eaecbd81] + - @backstage/core@0.7.7 + +## 0.8.0 + +### Minor Changes + +- ac6025f63: Add feedback link icon in Techdocs Reader that directs to GitLab or GitHub repo issue page with pre-filled title and source link. + For link to appear, requires `repo_url` and `edit_uri` to be filled in mkdocs.yml, as per . An `edit_uri` will need to be specified for self-hosted GitLab/GitHub instances with a different host name. + To identify issue URL format as GitHub or GitLab, the host name of source in `repo_url` is checked if it contains `gitlab` or `github`. Alternately this is determined by matching to `host` values from `integrations` in app-config.yaml. + +### Patch Changes + +- e292e393f: Add a test id to the shadow root element of the Reader to access it easily in e2e tests +- Updated dependencies [94da20976] +- Updated dependencies [d8cc7e67a] +- Updated dependencies [99fbef232] +- Updated dependencies [ab07d77f6] +- Updated dependencies [931b21a12] +- Updated dependencies [937ed39ce] +- Updated dependencies [9a9e7a42f] +- Updated dependencies [50ce875a0] + - @backstage/core@0.7.6 + - @backstage/theme@0.2.6 + +## 0.7.2 + +### Patch Changes + +- fef852ecd: Reworked the TechDocs plugin to support using the configured company name instead of + 'Backstage' in the page title. +- 18f7345a6: Add borders to TechDocs tables and increase font size. Fixes #5264 and #5276. +- Updated dependencies [bb5055aee] +- Updated dependencies [d0d1c2f7b] +- Updated dependencies [5d0740563] +- Updated dependencies [5cafcf452] +- Updated dependencies [86a95ba67] +- Updated dependencies [e27cb6c45] + - @backstage/catalog-model@0.7.7 + - @backstage/core@0.7.5 + +## 0.7.1 + +### Patch Changes + +- bebd1c4fe: Remove the `@backstage/techdocs-common` dependency to not pull in backend config schemas in the frontend. +- Updated dependencies [9f48b548c] +- Updated dependencies [8488a1a96] + - @backstage/plugin-catalog-react@0.1.4 + - @backstage/catalog-model@0.7.5 + +## 0.7.0 + +### Minor Changes + +- aaeb7ecf3: When newer documentation available but not built, show older documentation while async building newer + TechDocs backend: /sync endpoint added to support above, returns immediate success if docs don't need a build, returns delayed success after build if needed + TechDocs backend: /docs endpoint removed as frontend can directly request to techdocs.storageUrl or /static/docs +- 3139f83af: Add sticky sidebars and footer navigation links to TechDocs Reader + +### Patch Changes + +- ea9d977e7: Introduce workaround for admonition icons of MkDocs. +- 2aab54319: TechDocs: links at sidebar and bottom reset scroll position to top +- Updated dependencies [01ccef4c7] +- Updated dependencies [fcc3ada24] +- Updated dependencies [4618774ff] +- Updated dependencies [df59930b3] + - @backstage/plugin-catalog-react@0.1.3 + - @backstage/core@0.7.3 + - @backstage/theme@0.2.5 + +## 0.6.2 + +### Patch Changes + +- 83bfc98a3: On TechDocs page header, change the breadcrumbs link to be static and point to TechDocs homepage. +- e7baa0d2e: Separate techdocs-backend and frontend config schema declarations +- c8b54c370: Extended TechDocs HomePage with owned documents +- Updated dependencies [0434853a5] +- Updated dependencies [8686eb38c] +- Updated dependencies [9ca0e4009] +- Updated dependencies [34ff49b0f] +- Updated dependencies [8686eb38c] +- Updated dependencies [424742dc1] +- Updated dependencies [4e0b5055a] + - @backstage/config@0.1.4 + - @backstage/core@0.7.2 + - @backstage/plugin-catalog-react@0.1.2 + - @backstage/techdocs-common@0.4.5 + - @backstage/test-utils@0.1.9 + +## 0.6.1 + +### Patch Changes + +- aa095e469: OpenStack Swift publisher added for tech-docs. +- 2089de76b: Make use of the new core `ItemCardGrid` and `ItemCardHeader` instead of the deprecated `ItemCard`. +- 868e4cdf2: - Adds a link to the owner entity + - Corrects the link to the component which includes the namespace +- ca4a904f6: Add an optional configuration option for setting the url endpoint for AWS S3 publisher: `techdocs.publisher.awsS3.endpoint` +- Updated dependencies [d7245b733] +- Updated dependencies [0b42fff22] +- Updated dependencies [0b42fff22] +- Updated dependencies [2ef5bc7ea] +- Updated dependencies [ff4d666ab] +- Updated dependencies [aa095e469] +- Updated dependencies [2089de76b] +- Updated dependencies [dc1fc92c8] +- Updated dependencies [bc46435f5] +- Updated dependencies [a501128db] +- Updated dependencies [ca4a904f6] + - @backstage/techdocs-common@0.4.4 + - @backstage/catalog-model@0.7.4 + - @backstage/core@0.7.1 + - @backstage/theme@0.2.4 + +## 0.6.0 + +### Minor Changes + +- 813c6a4f2: Add authorization header on techdocs api requests. Breaking change as clients now needs the Identity API. + +### Patch Changes + +- Updated dependencies [12d8f27a6] +- Updated dependencies [f43192207] +- Updated dependencies [40c0fdbaa] +- Updated dependencies [2a271d89e] +- Updated dependencies [bece09057] +- Updated dependencies [169f48deb] +- Updated dependencies [8a1566719] +- Updated dependencies [9d455f69a] +- Updated dependencies [4c049a1a1] +- Updated dependencies [02816ecd7] +- Updated dependencies [61299519f] + - @backstage/catalog-model@0.7.3 + - @backstage/techdocs-common@0.4.3 + - @backstage/core@0.7.0 + - @backstage/plugin-catalog-react@0.1.1 + +## 0.5.8 + +### Patch Changes + +- f37992797: Got rid of some `attr` and cleaned up a bit in the TechDocs config schema. +- 2499f6cde: Add support for assuming role in AWS integrations +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [dc12852c9] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2499f6cde] +- Updated dependencies [a1f5e6545] +- Updated dependencies [1e4ddd71d] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/test-utils@0.1.8 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + - @backstage/techdocs-common@0.4.2 + - @backstage/config@0.1.3 + +## 0.5.7 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [fb28da212] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [26e143e60] +- Updated dependencies [c6655413d] +- Updated dependencies [44414239f] +- Updated dependencies [b0a41c707] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/techdocs-common@0.4.1 + - @backstage/plugin-catalog-react@0.0.4 + +## 0.5.6 + +### Patch Changes + +- f5e564cd6: Improve display of error messages +- 41af18227: Migrated to new composability API, exporting the plugin instance as `techdocsPlugin`, the top-level page as `TechdocsPage`, and the entity content as `EntityTechdocsContent`. +- 8f3443427: Enhance API calls to support trapping 500 errors from techdocs-backend +- Updated dependencies [77ad0003a] +- Updated dependencies [b51ee6ece] +- Updated dependencies [19d354c78] +- Updated dependencies [08142b256] +- Updated dependencies [08142b256] +- Updated dependencies [b51ee6ece] + - @backstage/techdocs-common@0.4.0 + - @backstage/test-utils@0.1.7 + - @backstage/plugin-catalog-react@0.0.3 + - @backstage/core@0.6.1 + +## 0.5.5 + +### Patch Changes + +- 5fa3bdb55: Add `href` in addition to `onClick` to `ItemCard`. Ensure that the height of a + `ItemCard` with and without tags is equal. +- e44925723: `techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [c777df180] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [f0320190d] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/techdocs-common@0.3.7 + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + +## 0.5.4 + +### Patch Changes + +- a5e27d5c1: Create type for TechDocsMetadata (#3716) + + This change introduces a new type (TechDocsMetadata) in packages/techdocs-common. This type is then introduced in the endpoint response in techdocs-backend and in the api interface in techdocs (frontend). + +- Updated dependencies [def2307f3] + +- Updated dependencies [efd6ef753] + +- Updated dependencies [593632f07] + +- Updated dependencies [33846acfc] + +- Updated dependencies [a187b8ad0] + +- Updated dependencies [f04db53d7] + +- Updated dependencies [53c9c51f2] + +- Updated dependencies [a5e27d5c1] + +- Updated dependencies [a93f42213] + - @backstage/catalog-model@0.7.0 + - @backstage/core@0.5.0 + - @backstage/plugin-catalog@0.2.12 + - @backstage/techdocs-common@0.3.5 + +## 0.5.3 + +### Patch Changes + +- dbe4450c3: Google Cloud authentication in TechDocs has been improved. + + 1. `techdocs.publisher.googleGcs.credentials` is now optional. If it is missing, `GOOGLE_APPLICATION_CREDENTIALS` + environment variable (and some other methods) will be used to authenticate. + Read more here + + 2. `techdocs.publisher.googleGcs.projectId` is no longer used. You can remove it from your `app-config.yaml`. + +- a6f9dca0d: Remove dependency on `@backstage/core-api`. No plugin should ever depend on that package; it's an internal concern whose important bits are re-exported by `@backstage/core` which is the public facing dependency to use. + +- b3b9445df: AWS S3 authentication in TechDocs has been improved. + + 1. `techdocs.publisher.awsS3.bucketName` is now the only required config. `techdocs.publisher.awsS3.credentials` and `techdocs.publisher.awsS3.region` are optional. + + 2. If `techdocs.publisher.awsS3.credentials` and `techdocs.publisher.awsS3.region` are missing, the AWS environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` will be used. There are more better ways of setting up AWS authentication. Read the guide at + +- e5d12f705: Use `history.pushState` for hash link navigation. + +- Updated dependencies [68ad5af51] + +- Updated dependencies [f3b064e1c] + +- Updated dependencies [371f67ecd] + +- Updated dependencies [f1e74777a] + +- Updated dependencies [dbe4450c3] + +- Updated dependencies [c00488983] + +- Updated dependencies [265a7ab30] + +- Updated dependencies [5826d0973] + +- Updated dependencies [b3b9445df] + +- Updated dependencies [abbee6fff] + +- Updated dependencies [147fadcb9] + - @backstage/techdocs-common@0.3.3 + - @backstage/catalog-model@0.6.1 + - @backstage/plugin-catalog@0.2.11 + - @backstage/core@0.4.4 + +## 0.5.2 + +### Patch Changes + +- 359f9d2d8: Added configuration schema for the commonly used properties of techdocs and techdocs-backend plugins +- Updated dependencies [a08c32ced] +- Updated dependencies [7e0b8cac5] +- Updated dependencies [8804e8981] +- Updated dependencies [87c0c53c2] +- Updated dependencies [86c3c652a] +- Updated dependencies [27f2af935] + - @backstage/core-api@0.2.8 + - @backstage/core@0.4.3 + - @backstage/plugin-catalog@0.2.9 + - @backstage/techdocs-common@0.3.1 + +## 0.5.1 + +### Patch Changes + +- Updated dependencies [d681db2b5] +- Updated dependencies [1dc445e89] +- Updated dependencies [342270e4d] +- Updated dependencies [1dc445e89] +- Updated dependencies [a8573e53b] + - @backstage/core-api@0.2.7 + - @backstage/core@0.4.2 + - @backstage/test-utils@0.1.6 + - @backstage/plugin-catalog@0.2.8 + - @backstage/techdocs-common@0.3.0 + +## 0.5.0 + +### Minor Changes + +- dae4f3983: _Breaking changes_ + + 1. Added option to use Google Cloud Storage as a choice to store the static generated files for TechDocs. + It can be configured using `techdocs.publisher.type` option in `app-config.yaml`. + Step-by-step guide to configure GCS is available here + Set `techdocs.publisher.type` to `'local'` if you want to continue using local filesystem to store TechDocs files. + + 2. `techdocs.builder` is now required and can be set to `'local'` or `'external'`. (Set it to `'local'` for now, since CI/CD build + workflow for TechDocs will be available soon (in few weeks)). + If builder is set to 'local' and you open a TechDocs page, `techdocs-backend` will try to generate the docs, publish to storage and + show the generated docs afterwords. + If builder is set to `'external'`, `techdocs-backend` will only fetch the docs and will NOT try to generate and publish. In this case of `'external'`, + we assume that docs are being built in the CI/CD pipeline of the repository. + TechDocs will not assume a default value for `techdocs.builder`. It is better to explicitly define it in the `app-config.yaml`. + + 3. When configuring TechDocs in your backend, there is a difference in how a new publisher is created. + + --- const publisher = new LocalPublish(logger, discovery); + +++ const publisher = Publisher.fromConfig(config, logger, discovery); + + Based on the config `techdocs.publisher.type`, the publisher could be either Local publisher or Google Cloud Storage publisher. + + 4. `techdocs.storageUrl` is now a required config. Should be `http://localhost:7000/api/techdocs/static/docs` in most setups. + + 5. Parts of `@backstage/plugin-techdocs-backend` have been moved to a new package `@backstage/techdocs-common` to generate docs. Also to publish docs + to-and-fro between TechDocs and a storage (either local or external). However, a Backstage app does NOT need to import the `techdocs-common` package - + app should only import `@backstage/plugin-techdocs` and `@backstage/plugin-techdocs-backend`. + + _Patch changes_ + + 1. See all of TechDocs config options and its documentation + + 2. Logic about serving static files and metadata retrieval have been abstracted away from the router in `techdocs-backend` to the instance of publisher. + + 3. Removed Material UI Spinner from TechDocs header. Spinners cause unnecessary UX distraction. + Case 1 (when docs are built and are to be served): Spinners appear for a split second before the name of site shows up. This unnecessarily distracts eyes because spinners increase the size of the Header. A dot (.) would do fine. Definitely more can be done. + Case 2 (when docs are being generated): There is already a linear progress bar (which is recommended in Storybook). + +### Patch Changes + +- Updated dependencies [c911061b7] +- Updated dependencies [dae4f3983] +- Updated dependencies [8ef71ed32] +- Updated dependencies [0e6298f7e] +- Updated dependencies [7dd2ef7d1] +- Updated dependencies [ac3560b42] + - @backstage/catalog-model@0.6.0 + - @backstage/techdocs-common@0.2.0 + - @backstage/core@0.4.1 + - @backstage/core-api@0.2.6 + - @backstage/plugin-catalog@0.2.7 + +## 0.4.0 + +### Minor Changes + +- 87a33d2fe: Removed modifyCss transformer and moved the css to injectCss transformer + Fixed issue where some internal doc links would cause a reload of the page + +### Patch Changes + +- Updated dependencies [b6557c098] +- Updated dependencies [2527628e1] +- Updated dependencies [6011b7d3e] +- Updated dependencies [e1f4e24ef] +- Updated dependencies [1c69d4716] +- Updated dependencies [d8d5a17da] +- Updated dependencies [83b6e0c1f] +- Updated dependencies [1665ae8bb] +- Updated dependencies [04f26f88d] +- Updated dependencies [ff243ce96] + - @backstage/core-api@0.2.5 + - @backstage/core@0.4.0 + - @backstage/plugin-catalog@0.2.6 + - @backstage/test-utils@0.1.5 + - @backstage/catalog-model@0.5.0 + - @backstage/theme@0.2.2 + +## 0.3.1 + +### Patch Changes + +- da2ad65cb: Use type EntityName from catalog-model for entities +- Updated dependencies [b4488ddb0] +- Updated dependencies [08835a61d] +- Updated dependencies [a9fd599f7] +- Updated dependencies [bcc211a08] +- Updated dependencies [ebf37bbae] + - @backstage/core-api@0.2.4 + - @backstage/catalog-model@0.4.0 + - @backstage/plugin-catalog@0.2.5 + - @backstage/test-utils@0.1.4 + +## 0.3.0 + +### Minor Changes + +- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. + - techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` + +### Patch Changes + +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [700a212b4] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/core-api@0.2.3 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies [475fc0aaa] +- Updated dependencies [1166fcc36] +- Updated dependencies [1185919f3] + - @backstage/core@0.3.2 + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-catalog@0.2.3 + +## 0.2.2 + +### Patch Changes + +- 1722cb53c: Added configuration schema +- Updated dependencies [1722cb53c] +- Updated dependencies [8b7737d0b] + - @backstage/core@0.3.1 + - @backstage/plugin-catalog@0.2.2 + - @backstage/test-utils@0.1.3 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies [c5bab94ab] +- Updated dependencies [7b37d65fd] +- Updated dependencies [4aca74e08] +- Updated dependencies [e8f69ba93] +- Updated dependencies [0c0798f08] +- Updated dependencies [0c0798f08] +- Updated dependencies [199237d2f] +- Updated dependencies [6627b626f] +- Updated dependencies [4577e377b] +- Updated dependencies [2d0bd1be7] + - @backstage/core-api@0.2.1 + - @backstage/core@0.3.0 + - @backstage/theme@0.2.1 + - @backstage/plugin-catalog@0.2.1 + +## 0.2.0 + +### Minor Changes + +- 28edd7d29: Create backend plugin through CLI +- 8351ad79b: Add a message if techdocs takes long time to load + + Fixes #2416. + + The UI after the change should look like this: + + ![techdocs-progress-bar](https://user-images.githubusercontent.com/33940798/94189286-296ac980-fec8-11ea-9051-1b3db938d12f.gif) + +### Patch Changes + +- 782f3b354: add test case for Progress component +- 57b54c8ed: While techdocs fetches site name and metadata for the component, the page title was displayed as '[object Object] | Backstage'. This has now been fixed to display the component ID if site name is not present or being fetched. +- Updated dependencies [28edd7d29] +- Updated dependencies [819a70229] +- Updated dependencies [3a4236570] +- Updated dependencies [ae5983387] +- Updated dependencies [0d4459c08] +- Updated dependencies [cbbd271c4] +- Updated dependencies [482b6313d] +- Updated dependencies [e0be86b6f] +- Updated dependencies [f70a52868] +- Updated dependencies [12b5fe940] +- Updated dependencies [368fd8243] +- Updated dependencies [1c60f716e] +- Updated dependencies [144c66d50] +- Updated dependencies [a768a07fb] +- Updated dependencies [b79017fd3] +- Updated dependencies [6d97d2d6f] +- Updated dependencies [5adfc005e] +- Updated dependencies [f0aa01bcc] +- Updated dependencies [0aecfded0] +- Updated dependencies [93a3fa3ae] +- Updated dependencies [782f3b354] +- Updated dependencies [8b9c8196f] +- Updated dependencies [2713f28f4] +- Updated dependencies [406015b0d] +- Updated dependencies [82759d3e4] +- Updated dependencies [60d40892c] +- Updated dependencies [ac8d5d5c7] +- Updated dependencies [2ebcfac8d] +- Updated dependencies [fa56f4615] +- Updated dependencies [ebca83d48] +- Updated dependencies [aca79334f] +- Updated dependencies [c0d5242a0] +- Updated dependencies [b3d57961c] +- Updated dependencies [0b956f21b] +- Updated dependencies [26e69ab1a] +- Updated dependencies [97c2cb19b] +- Updated dependencies [3beb5c9fc] +- Updated dependencies [cbab5bbf8] +- Updated dependencies [754e31db5] +- Updated dependencies [1611c6dbc] + - @backstage/plugin-catalog@0.2.0 + - @backstage/core-api@0.2.0 + - @backstage/core@0.2.0 + - @backstage/catalog-model@0.2.0 + - @backstage/theme@0.2.0 + - @backstage/test-utils@0.1.2 + +## @backstage/ui@0.9.0-next.1 + +### Minor Changes + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + Migration examples: + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + +### Patch Changes + +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. + +## @backstage/plugin-notifications-backend-module-email@0.3.16-next.0 + +# @backstage/plugin-notifications-backend-module-email + +## 0.3.15-next.0 + +### Patch Changes + +- 22a5362: Updated `AWS SES` client to version 2 to support `nodemailer` version 7. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.21-next.0 + - @backstage/config@1.3.6-next.0 + - @backstage/catalog-model@1.7.6-next.0 + - @backstage/integration-aws-node@0.1.19-next.0 + - @backstage/backend-plugin-api@1.4.5-next.0 + - @backstage/catalog-client@1.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.2-next.0 + - @backstage/plugin-notifications-common@0.1.2-next.0 + +## 0.3.14 + +### Patch Changes + +- b8cf31a: chore(deps): bump `nodemailer` from 6.9.16 to 7.0.7 +- f5e0963: Removed unused dependencies +- Updated dependencies + - @backstage/config@1.3.5 + - @backstage/backend-plugin-api@1.4.4 + - @backstage/integration-aws-node@0.1.18 + - @backstage/plugin-catalog-node@1.19.1 + - @backstage/plugin-notifications-common@0.1.1 + - @backstage/plugin-notifications-node@0.2.20 + +## 0.3.14-next.1 + +### Patch Changes + +- b8cf31a: chore(deps): bump `nodemailer` from 6.9.16 to 7.0.7 +- f5e0963: Removed unused dependencies + +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-notifications-node@0.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/catalog-client@1.12.0 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-node@0.2.19 + - @backstage/backend-plugin-api@1.4.3 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.12.0-next.0 + - @backstage/plugin-catalog-node@1.19.0-next.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-notifications-node@0.2.19-next.1 + +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.3-next.0 + - @backstage/plugin-catalog-node@1.18.1-next.0 + - @backstage/plugin-notifications-node@0.2.19-next.0 + +## 0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0 + - @backstage/plugin-catalog-node@1.18.0 + - @backstage/plugin-notifications-common@0.1.0 + - @backstage/plugin-notifications-node@0.2.18 + - @backstage/backend-plugin-api@1.4.2 + +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + +## 0.3.11 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## 0.3.11-next.1 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## 0.3.10 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.3.10-next.2 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## 0.3.9 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.3.9-next.3 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.1 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.0 + - @backstage/backend-plugin-api@1.3.1-next.0 + - @backstage/plugin-notifications-node@0.2.15-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.16.3 + - @backstage/backend-plugin-api@1.3.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.14 + +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.16.3-next.0 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.13 + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.15 + - @backstage/backend-plugin-api@1.2.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.16.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.13 + +## 0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.16.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.13-next.2 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.16.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.13-next.1 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.1-next.0 + - @backstage/plugin-catalog-node@1.16.1-next.0 + - @backstage/plugin-notifications-node@0.2.13-next.0 + +## 0.3.6 + +### Patch Changes + +- 6259aa9: Add transport for Azure Communication Service +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0 + - @backstage/plugin-catalog-node@1.16.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.12 + +## 0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.16.0-next.3 + - @backstage/backend-plugin-api@1.2.0-next.2 + - @backstage/plugin-notifications-node@0.2.12-next.2 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.2.0-next.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.16.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.12-next.1 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.16.0-next.1 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.12-next.0 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.15.2-next.0 + - @backstage/backend-plugin-api@1.2.0-next.0 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.12-next.0 + +## 0.3.5 + +### Patch Changes + +- bed5f35: Added more examples of the plugin configuration +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/backend-plugin-api@1.1.1 + - @backstage/catalog-client@1.9.1 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.15 + - @backstage/plugin-catalog-node@1.15.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.11 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/backend-plugin-api@1.1.1-next.1 + - @backstage/catalog-model@1.7.3-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/plugin-catalog-node@1.15.1-next.1 + - @backstage/integration-aws-node@0.1.15-next.0 + - @backstage/plugin-notifications-node@0.2.11-next.1 + - @backstage/catalog-client@1.9.1-next.0 + - @backstage/plugin-notifications-common@0.0.8-next.0 + +## 0.3.5-next.0 + +### Patch Changes + +- bed5f35: Added more examples of the plugin configuration +- Updated dependencies + - @backstage/backend-plugin-api@1.1.1-next.0 + - @backstage/catalog-client@1.9.0 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/integration-aws-node@0.1.14 + - @backstage/types@1.2.0 + - @backstage/plugin-catalog-node@1.15.1-next.0 + - @backstage/plugin-notifications-common@0.0.7 + - @backstage/plugin-notifications-node@0.2.11-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0 + - @backstage/plugin-catalog-node@1.15.0 + - @backstage/catalog-client@1.9.0 + - @backstage/plugin-notifications-node@0.2.10 + - @backstage/catalog-model@1.7.2 + - @backstage/config@1.3.1 + - @backstage/integration-aws-node@0.1.14 + - @backstage/types@1.2.0 + - @backstage/plugin-notifications-common@0.0.7 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.1.0-next.2 + - @backstage/plugin-catalog-node@1.15.0-next.2 + - @backstage/plugin-notifications-node@0.2.10-next.2 + - @backstage/catalog-client@1.9.0-next.2 + - @backstage/catalog-model@1.7.2-next.0 + - @backstage/config@1.3.1-next.0 + - @backstage/integration-aws-node@0.1.14-next.0 + - @backstage/types@1.2.0 + - @backstage/plugin-notifications-common@0.0.7-next.0 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.15.0-next.1 + - @backstage/catalog-client@1.9.0-next.1 + - @backstage/plugin-notifications-node@0.2.10-next.1 + - @backstage/backend-plugin-api@1.1.0-next.1 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/integration-aws-node@0.1.13 + - @backstage/types@1.2.0 + - @backstage/plugin-notifications-common@0.0.6 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.3-next.0 + - @backstage/catalog-client@1.8.1-next.0 + - @backstage/catalog-model@1.7.1 + - @backstage/config@1.3.0 + - @backstage/integration-aws-node@0.1.13 + - @backstage/types@1.2.0 + - @backstage/plugin-catalog-node@1.14.1-next.0 + - @backstage/plugin-notifications-common@0.0.6 + - @backstage/plugin-notifications-node@0.2.10-next.0 + +## 0.3.3 + +### Patch Changes + +- d52d7f9: Support ISO and ms string forms of durations in config too +- 5d74716: Remove unused backend-common dependency +- Updated dependencies + - @backstage/catalog-client@1.8.0 + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/integration-aws-node@0.1.13 + - @backstage/plugin-catalog-node@1.14.0 + - @backstage/backend-plugin-api@1.0.2 + - @backstage/plugin-notifications-common@0.0.6 + - @backstage/plugin-notifications-node@0.2.9 + - @backstage/catalog-model@1.7.1 + +## 0.3.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.13-next.0 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/catalog-client@1.8.0-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.14.0-next.2 + - @backstage/plugin-notifications-common@0.0.6-next.0 + - @backstage/plugin-notifications-node@0.2.9-next.3 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.14.0-next.2 + - @backstage/plugin-notifications-node@0.2.9-next.2 + - @backstage/backend-plugin-api@1.0.2-next.2 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.6-next.0 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.2-next.1 + - @backstage/catalog-client@1.8.0-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.14.0-next.1 + - @backstage/plugin-notifications-common@0.0.6-next.0 + - @backstage/plugin-notifications-node@0.2.9-next.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.14.0-next.0 + - @backstage/plugin-notifications-common@0.0.6-next.0 + - @backstage/catalog-client@1.8.0-next.0 + - @backstage/backend-plugin-api@1.0.2-next.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-node@0.2.9-next.0 + +## 0.3.1 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.7 + - @backstage/plugin-catalog-node@1.13.1 + - @backstage/catalog-client@1.7.1 + - @backstage/backend-plugin-api@1.0.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.13.1-next.1 + - @backstage/catalog-client@1.7.1-next.0 + - @backstage/backend-plugin-api@1.0.1-next.1 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.7-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- 094eaa3: Remove references to in-repo backend-common +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.7-next.0 + - @backstage/backend-plugin-api@1.0.1-next.0 + - @backstage/catalog-client@1.7.0 + - @backstage/catalog-model@1.7.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.13.1-next.0 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.3.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0 + - @backstage/backend-plugin-api@1.0.0 + - @backstage/catalog-model@1.7.0 + - @backstage/catalog-client@1.7.0 + - @backstage/plugin-catalog-node@1.13.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + +## 0.3.0-next.0 + +### Minor Changes + +- d425fc4: **BREAKING**: The return values from `createBackendPlugin`, `createBackendModule`, and `createServiceFactory` are now simply `BackendFeature` and `ServiceFactory`, instead of the previously deprecated form of a function that returns them. For this reason, `createServiceFactory` also no longer accepts the callback form where you provide direct options to the service. This also affects all `coreServices.*` service refs. + + This may in particular affect tests; if you were effectively doing `createBackendModule({...})()` (note the parentheses), you can now remove those extra parentheses at the end. You may encounter cases of this in your `packages/backend/src/index.ts` too, where you add plugins, modules, and services. If you were using `createServiceFactory` with a function as its argument for the purpose of passing in options, this pattern has been deprecated for a while and is no longer supported. You may want to explore the new multiton patterns to achieve your goals, or moving settings to app-config. + + As part of this change, the `IdentityFactoryOptions` type was removed, and can no longer be used to tweak that service. The identity service was also deprecated some time ago, and you will want to [migrate to the new auth system](https://backstage.io/docs/tutorials/auth-service-migration) if you still rely on it. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.0 + - @backstage/backend-common@0.25.0-next.0 + - @backstage/plugin-notifications-node@0.2.6-next.0 + - @backstage/catalog-client@1.6.6 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.2.0 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

    ${notification.content}

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

    ${notification.content}

    `; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + +## 0.1.3 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.21-next.0 + - @backstage/backend-common@0.23.2-next.0 + - @backstage/plugin-notifications-node@0.2.2-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + +## 0.1.0 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- 78a0b08: Internal refactor to handle `BackendFeature` contract change. +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-common@0.23.0 + - @backstage/backend-plugin-api@0.6.19 + - @backstage/plugin-notifications-node@0.2.0 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.1.0-next.3 + +### Patch Changes + +- d44a20a: Added additional plugin metadata to `package.json`. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.3 + - @backstage/plugin-notifications-common@0.0.4-next.0 + - @backstage/plugin-notifications-node@0.2.0-next.3 + - @backstage/backend-common@0.23.0-next.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.2 + - @backstage/backend-common@0.23.0-next.2 + - @backstage/plugin-notifications-node@0.2.0-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + +## 0.1.0-next.1 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## 0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-notifications-node@0.1.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + +## 0.0.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + +## 0.0.1-next.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + +## 0.0.1-next.0 + +### Patch Changes + +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/plugin-notifications-node@0.1.4-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/create-app@0.7.6-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + +## @backstage/plugin-mui-to-bui@0.2.1-next.1 + +### Patch Changes + +- 5c614ff: Updated BUI checkbox preview example to align with new component API. +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + +## example-app@0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + - @backstage/plugin-mui-to-bui@0.2.1-next.1 + +## example-app-next@0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + +## techdocs-cli-embedded-app@0.2.114-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 diff --git a/docs/releases/v1.45.0-next.2-changelog.md b/docs/releases/v1.45.0-next.2-changelog.md new file mode 100644 index 0000000000..4bac9094e7 --- /dev/null +++ b/docs/releases/v1.45.0-next.2-changelog.md @@ -0,0 +1,1448 @@ +# Release v1.45.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.45.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.45.0-next.2) + +## @backstage/backend-app-api@1.3.0-next.1 + +### Minor Changes + +- a17d9df: Updates API for `instanceMetadata` service to return a list of plugins not features. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/backend-plugin-api@1.5.0-next.1 + +### Minor Changes + +- a17d9df: Promote `instanceMetadata` service to main entrypoint. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/ui@0.9.0-next.2 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + Migration: + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + +- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. +- 1059f95: Improved the Link component structure in Backstage UI. +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. +- e16ece5: Set the color-scheme property depending on theme + +## @backstage/plugin-catalog@1.32.0-next.1 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-catalog-backend@3.2.0-next.1 + +### Minor Changes + +- 2d229b2: Enable YAML merge keys in yamlPlaceholderResolver +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.0-next.1 + +### Minor Changes + +- 980f240: Moved from `ldapjs` dependency to `ldapts` + + ### Breaking Changes + + **Type Migration** + + Custom transformers must now accept `Entry` from ldapts instead of `SearchEntry` + from ldapjs The Entry type provides direct property access without need for + `.object()` or `.raw()` methods. + + If you have custom user or group transformers, update the signature from: + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: SearchEntry) => + Promise; + ``` + + to + + ```typescript + (vendor: LdapVendor, config: UserConfig, entry: Entry) => + Promise; + ``` + + **Search Options** + + Updated LDAP search configuration `typesOnly: false` → `attributeValues: true` + This inverts the boolean logic: ldapjs used negative form while ldapts uses + positive form. Both achieve the same result: retrieving attribute values rather + than just attribute names. + + Update LDAP search options in configuration from + + ```yaml + options: + typesOnly: false + ``` + + to + + ```yaml + options: + attributeValues: true + ``` + + **API Changes** Removed `LdapClient.searchStreaming()` method. Users should + migrate to `LdapClient.search()` instead + + If you're using `searchStreaming` directly: + + ```typescript + // Before + await client.searchStreaming(dn, options, async entry => { + // process each entry + }); + + // After + const entries = await client.search(dn, options); + for (const entry of entries) { + // process each entry + } + ``` + + > **_NOTE:_**: Both methods have always loaded all entries into memory. The + > searchStreaming method was only needed internally to handle ldapjs's + > event-based API. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-node@1.20.0-next.1 + +### Minor Changes + +- 9d3ec06: Make YAML merge (<<:) support configurable in the Backstage Catalog instead of always being enabled +- 8c26af4: Enable YAML merge keys in yamlPlaceholderResolver + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-gateway-backend@1.1.0-next.1 + +### Minor Changes + +- a17d9df: Update usage of the `instanceMetadata` service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0-next.1 + +### Minor Changes + +- ff96d7e: fix scaffolder action createDeployToken to allow usage of oauth tokens + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-search@1.5.0-next.1 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-search-react@1.10.0-next.1 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + +## @backstage/plugin-techdocs@1.16.0-next.1 + +### Minor Changes + +- a521911: Add support for customizable icons in `SearchResultListItemBlueprint` and related components + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/backend-defaults@0.13.1-next.1 + +### Patch Changes + +- 91ab2eb: Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-app-api@1.3.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/backend-dynamic-feature-service@0.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-app-node@0.1.39-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/backend-openapi-utils@0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/backend-test-utils@1.10.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-app-api@1.3.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/cli@0.34.5-next.1 + +### Patch Changes + +- da19cb5: Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. +- b2bef92: Convert all enums to erasable-syntax compliant patterns + +## @backstage/core-app-api@1.19.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.1 + +## @backstage/core-components@0.18.3-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.1 + +## @backstage/core-plugin-api@1.11.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns + +## @backstage/create-app@0.7.6-next.2 + +### Patch Changes + +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. + +## @backstage/frontend-plugin-api@0.12.2-next.1 + +### Patch Changes + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + +## @backstage/repo-tools@0.15.4-next.1 + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-api-docs@0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-app-backend@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-app-node@0.1.39-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-app-node@0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-app-visualizer@0.1.25-next.1 + +### Patch Changes + +- e81b3f0: Improve tree visualizer to use a horizontal layout and fill the content space. +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + +## @backstage/plugin-auth-backend@0.25.6-next.1 + +### Patch Changes + +- 51ff7d8: Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. + + Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-auth-node@0.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.11.2-next.1 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of repositories. Please see the [GitHub Discovery documentation](https://backstage.io/docs/integrations/github/discovery#configuration) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.16-next.1 + +### Patch Changes + +- 999d1c1: Added configurable `pageSizes` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. Please see the [GitHub Org Data documentation](https://backstage.io/docs/integrations/github/org#configuration-details) for new configuration options. +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.11.2-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.5-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.6-next.1 + +### Patch Changes + +- 70745c5: Correctly handle entity removal computation when DB count query returns string +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.16-next.1 + +### Patch Changes + +- a5bcb2a: fix wrong dereferencing for AsyncApi 3 documents +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-catalog-graph@0.5.3-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-catalog-react@1.21.3-next.1 + +### Patch Changes + +- 2b7924b: Apply default ordering of templates +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-devtools-backend@0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-devtools-common@0.1.19-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-devtools-common@0.1.19-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3-next.1 + +## @backstage/plugin-events-backend@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-backend-module-kafka@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-events-node@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-kubernetes-backend@0.20.4-next.1 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-kubernetes-node@0.3.6-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-kubernetes-node@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-notifications-backend@0.5.12-next.1 + +### Patch Changes + +- 15fb764: Show default settings for notifications even before receiving first notification. + + Previously, it was not possible for the users to see or modify their notification settings until they had received at + least one notification from specific origin or topic. + This update ensures that default settings are displayed from the outset, + allowing users to customize their preferences immediately. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-notifications-node@0.2.21-next.1 + - @backstage/plugin-signals-node@0.1.26-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-notifications-node@0.2.21-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-notifications-node@0.2.21-next.1 + +## @backstage/plugin-notifications-node@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-signals-node@0.1.26-next.1 + +## @backstage/plugin-permission-backend@0.7.6-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + +## @backstage/plugin-permission-common@0.9.3-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns + +## @backstage/plugin-permission-node@0.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + +## @backstage/plugin-proxy-backend@0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-proxy-node@0.1.10-next.1 + +## @backstage/plugin-proxy-node@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-scaffolder-backend@3.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-notifications-node@0.2.21-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.15-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.5-next.1 + +## @backstage/plugin-scaffolder-node@0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-test-utils@1.10.0-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + +## @backstage/plugin-search-backend@2.0.8-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.8-next.1 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-explore@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.50-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + - @backstage/plugin-techdocs-node@1.13.9-next.1 + +## @backstage/plugin-search-backend-node@1.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + +## @backstage/plugin-signals@0.0.25-next.1 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- f0f006e: Fixes a bug where the `SignalClient` would try to subscribe to the same channel twice after an error, instead of just once. +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/core-compat-api@0.5.4-next.0 + +## @backstage/plugin-signals-backend@0.3.10-next.1 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-signals-node@0.1.26-next.1 + +## @backstage/plugin-signals-node@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + +## @backstage/plugin-techdocs-backend@2.1.2-next.1 + +### Patch Changes + +- 71c22f3: Removed/moved unused dependencies +- Updated dependencies + - @backstage/plugin-catalog-node@1.20.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-techdocs-node@1.13.9-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + +### Patch Changes + +- 6929480: ExpandableCollapse Techdocs Addon was breaking native sidebar collapse on Firefox +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + +## @backstage/plugin-techdocs-node@1.13.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + +## @backstage/plugin-user-settings-backend@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-signals-node@0.1.26-next.1 + +## example-app@0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-signals@0.0.25-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-search@1.5.0-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + - @backstage/plugin-catalog-graph@0.5.3-next.1 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/plugin-api-docs@0.13.1-next.1 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + +## example-app-next@0.0.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-signals@0.0.25-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-search@1.5.0-next.1 + - @backstage/plugin-app-visualizer@0.1.25-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + - @backstage/plugin-catalog-graph@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/core-compat-api@0.5.4-next.0 + - @backstage/plugin-api-docs@0.13.1-next.1 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + +## example-backend@0.0.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.8-next.1 + - @backstage/plugin-kubernetes-backend@0.20.4-next.1 + - @backstage/plugin-techdocs-backend@2.1.2-next.1 + - @backstage/plugin-signals-backend@0.3.10-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/plugin-notifications-backend@0.5.12-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.16-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-backend@0.7.6-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-search-backend@2.0.8-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.6-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.5-next.1 + - @backstage/plugin-scaffolder-backend@3.0.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.8-next.1 + - @backstage/plugin-app-backend@0.5.8-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.9-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.14-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.2-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-devtools-backend@0.5.11-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.6-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-proxy-backend@0.6.8-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.9-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + +## techdocs-cli-embedded-app@0.2.114-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + +## @internal/plugin-todo-list-backend@1.0.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 diff --git a/docs/releases/v1.45.0-next.3-changelog.md b/docs/releases/v1.45.0-next.3-changelog.md new file mode 100644 index 0000000000..0f66b0ca65 --- /dev/null +++ b/docs/releases/v1.45.0-next.3-changelog.md @@ -0,0 +1,282 @@ +# Release v1.45.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.45.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.45.0-next.3) + +## @backstage/backend-plugin-api@1.5.0-next.2 + +### Minor Changes + +- 62fc2de: Explicitly mark `coreServices.rootInstanceMetadata` as a root service. + +## @backstage/repo-tools@0.16.0-next.2 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + +## @backstage/ui@0.9.0-next.3 + +### Minor Changes + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + +### Patch Changes + +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. +- 836b0c7: Fixed dialog backdrop appearance in dark mode. +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the href prop. + +## @backstage/plugin-notifications-backend@0.6.0-next.2 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-notifications-node@0.2.21-next.2 + +## @backstage/plugin-notifications-common@0.2.0-next.1 + +### Minor Changes + +- 87e597c: Adds support for default configuration for an entire notification channel. + This setting will also be inherited down to origins and topics while still respecting the users individual choices. + + This will be handy if you want to use a "opt-in" strategy. + +## @backstage/backend-test-utils@1.10.0-next.2 + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + +## @backstage/core-components@0.18.3-next.2 + +### Patch Changes + +- 96ad674: Line numbers in LogViewer will not be selectable in UI anymore + +## @backstage/create-app@0.7.6-next.3 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/frontend-plugin-api@0.12.2-next.2 + +### Patch Changes + +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- Updated dependencies + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-app-visualizer@0.1.25-next.2 + +### Patch Changes + +- 722e2df: Migrated to use `@backstage/ui`. +- Updated dependencies + - @backstage/ui@0.9.0-next.3 + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-catalog-graph@0.5.3-next.2 + +### Patch Changes + +- a2d7ae7: Ensure the catalog graph entity card respects the height prop so the visualization scales down properly on wide screens. +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3-next.2 + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-catalog-react@1.21.3-next.2 + +### Patch Changes + +- 36d7582: Added missing i18n +- Updated dependencies + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-home@0.8.14-next.1 + +### Patch Changes + +- 2ac5d29: Allow customization of VisitList by adding optional enrichVisit, transformPathname, canSave functions to VisitsStorageApi, along with VisitDisplayProvider for colors, labels +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3-next.2 + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-kubernetes-backend@0.20.4-next.2 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.8-next.1 + - @backstage/plugin-kubernetes-node@0.3.6-next.2 + +## @backstage/plugin-kubernetes-common@0.9.8-next.1 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. + +## @backstage/plugin-kubernetes-node@0.3.6-next.2 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.8-next.1 + +## @backstage/plugin-kubernetes-react@0.5.13-next.1 + +### Patch Changes + +- 1906d37: Updated dependency `@kubernetes/client-node` to `1.4.0`. +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.8-next.1 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-notifications@0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-notifications-backend-module-email@0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-notifications-node@0.2.21-next.2 + +## @backstage/plugin-notifications-backend-module-slack@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-notifications-node@0.2.21-next.2 + +## @backstage/plugin-notifications-node@0.2.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.2 + +## @backstage/plugin-org@0.6.46-next.1 + +### Patch Changes + +- 6db9e7e: Improved responsiveness of GroupProfileCard component +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.3-next.2 + - @backstage/frontend-plugin-api@0.12.2-next.2 + - @backstage/core-components@0.18.3-next.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.2.0-next.1 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-notifications-node@0.2.21-next.2 + +## example-backend@0.0.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-backend@0.6.0-next.2 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-kubernetes-backend@0.20.4-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.2 + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-scaffolder-backend@3.0.1-next.1 + - @backstage/plugin-search-backend@2.0.8-next.1 diff --git a/docs/releases/v1.45.0.md b/docs/releases/v1.45.0.md new file mode 100644 index 0000000000..2afc9e7c35 --- /dev/null +++ b/docs/releases/v1.45.0.md @@ -0,0 +1,72 @@ +--- +id: v1.45.0 +title: v1.45.0 +description: Backstage Release v1.45.0 +--- + +These are the release notes for the v1.45.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 + +### Backstage UI: Breaking Changes + New Components + +Multiple components migrated from Base UI to React Aria Components including `Avatar`, `Checkbox`, and removal of the `Collapsible` component in favour of `Accordion`. + +Check the [CHANGELOG.md](https://github.com/backstage/backstage/blob/master/packages/ui/CHANGELOG.md) for more migration guides on any breaking changes that come with these latest updates. + +### BREAKING: `ldapjs` -> `ldapts` + +Moved from deprecated `ldapjs` dependency to `ldapts`, with breaking changes to custom transformer types and search options. + +Contributed by [@ganives](https://github.com/ganievs) in [#30594](https://github.com/backstage/backstage/pull/30594) + +Check the [CHANGELOG.md](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/CHANGELOG.md) for more migration guides on any breaking changes that come with these latest updates. + +### NFS: Plugin-Relative Extension Attachments + +Added support for plugin-relative `attachTo` declarations for extension definitions, allowing extensions to attach to other extensions of a particular kind in the same plugin rather than requiring exact extension IDs. + +### NFS: Forwards Compatibility for Route Refs + +It is now possible to use route references from the old frontend system directly in the new one. That means there's no longer a need to use `convertLegacyRouteRef` or `convertLegacyRouteRefs` to re-use route refs in implementations for the new system. This both simplifies migration, and reduces risk for cross system issues while partially migrated. + +This requires no immediate action on your part. As long as a plugin intends to support the old system, it can still keep defining its route refs using the old system without issues. And calling `convertLegacyRouteRef` or `convertLegacyRouteRefs` does not cause problems. + +### Configurable Dynamic Client Registration Token Expiration + +Allow configuring dynamic client registration token expiration with config `auth.experimentalDynamicClientRegistration.tokenExpiration`. Maximum expiration for the DCR token is 24 hours. Default expiration is 1 hour. Contributed by [@drodil](https://github.com/drodil) in [#31278](https://github.com/backstage/backstage/pull/31278) + +### Support for Bitbucket Cloud API tokens + +Since Bitbucket Cloud is phasing out support for the `appPassword` tokens, you can now instead specify the more modern API tokens using the `token` field of your Bitbucket config. + +### Support for PostgreSQL 18 + +The default setup for `TestDatabases` will start running tests against PG14 and PG18 where available, instead of PG13 and PG17 as previously. If you pass in explicit database IDs to your test database instance, those will still be respected. + +### `coreServices.rootInstanceMetadata` is stable + +The backend service `coreServices.rootInstanceMetadata` is now available as stable. It currently has one method - that lets you list all of the installed backend plugins and their modules on the current instance. + +## 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.45.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.46.0-next.0-changelog.md b/docs/releases/v1.46.0-next.0-changelog.md new file mode 100644 index 0000000000..6947caf67a --- /dev/null +++ b/docs/releases/v1.46.0-next.0-changelog.md @@ -0,0 +1,2269 @@ +# Release v1.46.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.0) + +## @backstage/backend-app-api@1.4.0-next.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.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.14.0-next.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies + +### 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-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend@3.1.0-next.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 + +- 8f4aded: Fixing OpenAPI definition +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/config@1.3.6 + - @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-search-backend-node@1.4.0-next.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @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/app-defaults@1.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-plugin-api@1.5.1-next.0 + +### 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-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/backend-test-utils@1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @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.34.6-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/release-manifests@0.0.13 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/types@1.2.2 + +## @backstage/cli-common@0.1.16-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks + +## @backstage/cli-node@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/codemods@0.1.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## @backstage/config-loader@1.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.3-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.12.1-next.0 + +### 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-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.7-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## @backstage/dev-utils@1.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/frontend-app-api@0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + +## @backstage/frontend-plugin-api@0.13.2-next.0 + +### Patch Changes + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- 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/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.18.3-next.0 + +### Patch Changes + +- e15fdae: Made the github urls case insensitive. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + +## @backstage/repo-tools@0.16.1-next.0 + +### Patch Changes + +- 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## @backstage/test-utils@1.7.14-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/theme@0.7.1-next.0 + +### 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/ui@0.9.1-next.0 + +### Patch Changes + +- 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 + +- 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-api-docs@0.13.2-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-app@0.3.3-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-app-backend@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-app-node@0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config-loader@1.10.7-next.0 + +## @backstage/plugin-app-visualizer@0.1.26-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-auth@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend@0.25.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.10-next.0 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-react@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + +## @backstage/plugin-catalog@1.32.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend@3.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @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-backend-module-aws@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @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-catalog-backend-module-ldap@0.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @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-catalog-backend-module-logs@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.11 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-graph@0.5.4-next.0 + +### Patch Changes + +- a67c144: Fixed zoom out description (replace `&` with &) +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.8-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-catalog-node@1.20.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @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.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/frontend-test-utils@0.4.2-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @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-catalog-unprocessed-entities@0.2.24-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools@0.1.34-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.19 + +## @backstage/plugin-devtools-backend@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @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.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @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.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-azure@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @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.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-test-utils@0.1.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.0 + +## @backstage/plugin-events-node@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-home@0.8.15-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-home-react@0.1.33-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-home-react@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-kubernetes@0.12.14-next.0 + +### 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-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-backend@0.20.5-next.0 + +### Patch Changes + +- 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) +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-kubernetes-node@0.3.7-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-node@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-kubernetes-react@0.5.14-next.0 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.8 + +## @backstage/plugin-mcp-actions-backend@0.1.6-next.0 + +### Patch Changes + +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mui-to-bui@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## @backstage/plugin-notifications@0.5.12-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend@0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-backend-module-slack@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-notifications-node@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @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.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-org-react@0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-permission-backend@0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @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.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-node@0.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-react@0.4.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-proxy-backend@0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-proxy-node@0.1.11-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-proxy-node@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + +## @backstage/plugin-scaffolder@1.34.4-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @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-techdocs-common@0.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-notifications-node@0.2.22-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-common@1.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @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-node@0.12.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @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-node-test-utils@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/backend-test-utils@1.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-react@1.19.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-search@1.5.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/core-components@0.18.4-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-backend@2.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.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-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-react@1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-signals@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-backend@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-node@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-react@0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.16.1-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + +## @backstage/plugin-techdocs-backend@2.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-catalog-node@1.20.1-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @backstage/plugin-techdocs-node@1.13.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.30-next.0 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-signals-react@0.0.18-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-signals-node@0.1.27-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.2-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @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.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## example-app-next@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-app-visualizer@0.1.26-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @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.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## app-next-example-plugin@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-components@0.18.4-next.0 + +## example-backend@0.0.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@3.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.20.5-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.6-next.0 + - @backstage/plugin-search-backend@2.0.9-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.0 + - @backstage/plugin-app-backend@0.5.9-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.0 + - @backstage/plugin-permission-backend@0.7.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-proxy-backend@0.6.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-devtools-backend@0.5.12-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.0 + - @backstage/plugin-notifications-backend@0.6.1-next.0 + - @backstage/plugin-signals-backend@0.3.11-next.0 + - @backstage/plugin-techdocs-backend@2.1.3-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-permission-common@0.9.3 + +## e2e-test@0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/create-app@0.7.7-next.0 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + +## techdocs-cli-embedded-app@0.2.115-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## yarn-plugin-backstage@0.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/release-manifests@0.0.13 + - @backstage/errors@1.2.7 + +## @internal/plugin-todo-list@1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + +## @internal/plugin-todo-list-backend@1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.46.0-next.1-changelog.md b/docs/releases/v1.46.0-next.1-changelog.md new file mode 100644 index 0000000000..42a188ec7f --- /dev/null +++ b/docs/releases/v1.46.0-next.1-changelog.md @@ -0,0 +1,392 @@ +# Release v1.46.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.1) + +## @backstage/plugin-techdocs-addons-test-utils@2.0.0-next.1 + +### 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/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + +## @backstage/ui@0.10.0-next.1 + +### 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 + +- 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 + +## @backstage/cli@0.34.6-next.1 + +### Patch Changes + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/cli-common@0.1.16-next.1 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. + +## @backstage/cli-node@0.2.16-next.1 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/codemods@0.1.53-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/core-components@0.18.4-next.1 + +### 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 + +- 5d52dab: Add i18n support for LogViewer search control + +## @backstage/create-app@0.7.7-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/dev-utils@1.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/repo-tools@0.16.1-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## @techdocs/cli@1.10.3-next.1 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## @backstage/plugin-app-visualizer@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.1 + +### 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. + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.1 + +### 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-catalog-backend-module-github@0.11.3-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## @backstage/plugin-catalog-react@1.21.4-next.1 + +### Patch Changes + +- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + +### 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. + +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +### 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-kubernetes@0.12.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-backend@0.20.5-next.1 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-kubernetes-node@0.3.7-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-common@0.9.9-next.0 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + +## @backstage/plugin-kubernetes-node@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-kubernetes-react@0.5.14-next.1 + +### Patch Changes + +- f966a85: Enabled a pod terminal at GKE +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-mui-to-bui@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + +## @backstage/plugin-scaffolder-react@1.19.4-next.1 + +### Patch Changes + +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/plugin-techdocs@1.16.1-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## @backstage/plugin-techdocs-backend@2.1.3-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/core-components@0.18.4-next.1 + +## example-app@0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## example-app-next@0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## techdocs-cli-embedded-app@0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 58e973d891..237a7207a1 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/core-plugin-api.createapiref.md) to create a - new [`ApiRef`](../reference/core-plugin-api.apiref.md) -- register an [`ApiFactory`](../reference/core-plugin-api.apifactory.md) with +- 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 your plugin using - [`createApiFactory`](../reference/core-plugin-api.createapifactory.md). This + [`createApiFactory`](../reference/frontend-plugin-api.createapifactory.md). 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/core-plugin-api.useapi.md) + [`useApi`](../reference/frontend-plugin-api.useapi.md) ### 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/core-plugin-api.discoveryapi.md) or the -> [FetchApi](../reference/core-plugin-api.fetchapi.md) +> [DiscoveryApi](../reference/frontend-plugin-api.discoveryapi.md) or the +> [FetchApi](../reference/frontend-plugin-api.fetchapi.md) ### 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/core-plugin-api.useapi.md) from within your plugin code. +[`useApi`](../reference/frontend-plugin-api.useapi.md) 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-11-25-backstagecon-kubecon-25-atlanta.mdx b/microsite/blog/2025-11-25-backstagecon-kubecon-25-atlanta.mdx new file mode 100644 index 0000000000..62f8c36dae --- /dev/null +++ b/microsite/blog/2025-11-25-backstagecon-kubecon-25-atlanta.mdx @@ -0,0 +1,93 @@ +--- +title: 'Backstage in Atlanta: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2025' +author: Kate Klymkovska, Spotify & André Wanlin, Spotify +--- + +![Backstage in Atlanta: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2025](assets/2025-11-25/backstagecon-kubecon-na-2025-header.png) + +Cancelled flights and wonky Wi-Fi couldn't hold us back from having an amazing time in the Big Peach! It was great to head south and commune with the community in person at [BackstageCon](https://colocatedeventsna2025.sched.com/overview/type/BackstageCon) and [KubeCon + CloudNativeCon North America 2025](https://kccncna2025.sched.com) in Atlanta. We gave talks, shared insights, chatted without having to type into little text boxes, and tackled issues elbow-to-elbow during [ContribFest](https://backstage.io/blog/2025/10/15/backstage-contribfest-kubecon-guide). The week was capped off with the [Backstage maintainers talk](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) where we celebrated a year of milestones, the release of the adoption-ready New Frontend System, and work towards an AI-Native Backstage. Catch up with recordings of [all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) and check out highlights from our week in the Big A below. + +{/* truncate */} + +## Scenes from BackstageCon (and beyond) + +![Scenes from BackstageCon and KubeCon + CloudNativeCon NA 2025](assets/2025-11-25/kcon25-atl-slideshow.gif) +_📸 [CNCF](https://www.flickr.com/photos/143247548@N03/albums/72177720330018728/)_ + +Brrr…it was a cold start with “Hot 'Lanta” bringing a serious autumn chill on Monday morning. But all the spicy DevEx insights would soon heat things up in the halls of the Georgia World Conference Center. Throughout the day, we heard discussions around how organizations adopt Backstage and scale it for productivity, as well as practical lessons on governance, deployment models, change management, and standardizing best practices around Backstage. + +And despite an unfortunate amount of flight disruptions waylaying some of the speakers, the organizers were able to work around all the last-minute issues for a day full of presentations, demos, lightning talks, and keynotes. Check out our community YouTube channel to watch recordings of [all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from Atlanta. Not sure where to start? + +### A few personal picks + +Here are six BackstageCon talks that we attended and thought were worth sharing. They feature Backstage adopters across a range of industries — from finance and healthcare, to networking, automotive experiences, and beyond — all sharing use cases that relate to almost any engineering org: + +- 🏦 [Supercharging Backstage Scaffolder for Workflows](https://www.youtube.com/watch?v=0m94ERF0k4Q&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Jonathan Chan and Francis Hackenberger, engineers from the Royal Bank of Canada's cloud team, talk about how they scale up the power of Backstage Software Templates at one of Canada's largest banks. + +- 🩺 [From Friction to Flow: How Backstage Transformed Our Security Experience](https://www.youtube.com/watch?v=ZCM9i1zD364&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): In this lightning talk, Christopher Diaz, principal engineer at RVO Health, squeezes three years' worth of Backstage learnings into a 10-minute lightning talk — including how Backstage has unlocked an inner source culture within their org, with teams building custom plugins to extend their instance (“That's what makes it so much fun to build on Backstage: everything you need to solve a problem is literally just a pull request away” 🙌). + +- 📶 [How Ericsson Scaled Developer Experience with Backstage](https://www.youtube.com/watch?v=1mDEkiN4NTs&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Ericsson principal engineer Damien O'Toole and engineering manager Kieran Egan share what they learned on the way to scaling internal Backstage adoption to over 1,260 users and over 4,073 weekly visits. + +- 🚗 [Cruising the AI Highway: Standardized MCP Deployment and Governance with Backstage](https://www.youtube.com/watch?v=pFSSpDHB2rU&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Engineers from Toyota Connected — “recovering system administrator” Rob Heckel and platform engineer Tyler Rothenberg — share how they deploy MCP servers across their org in an easy, secure, and sustainable way, covering everything from the basics of MCP to deploying MCP servers using cloud native tools and integrating them with Backstage. + +- 📊 [Lego Bricks for Developers: Turning Insights into Golden Paths at Neo4j](https://www.youtube.com/watch?v=IdLXeK8motg&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): Developer advocate Jeremy Adams (stepping in for a waylayed Chris Heisz) joins engineer Luke Beamish to talk about how they use Golden Paths at Neo4j, a leader in graph technology whose tech is used for cancer research, GenAI, and NASA's mission to Mars. + +- 🔔 [Driving Adoption by Generating High Value Notifications in Backstage](https://www.youtube.com/watch?v=N4gCLbhxTHA&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU): How do notifications work in Backstage and what makes them such a powerful way to drive Backstage adoption within your org? Watch this talk by Spotify engineering manager Avantika Iyer and customer success engineer/Backstage community maintainer André Wanlin. [Hey, that's me! Seriously, tho, notifications are the jam. — AW] + +### Backstage at KubeCon + +The Backstage-related talks continued throughout the week during KubeCon + CloudNativeCon, including: + +- 🚀 [The Journey of Deploying Backstage in a Large Organization](https://www.youtube.com/watch?v=l7OQdrUkoVI&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU), as told by engineers Mathieu Girard and Teddy Poingt from Beneva, Canada's largest mutual insurance company. + +- 📚 [Build Your Internal Developer Platform with the Experts](https://www.youtube.com/watch?v=eqxKB6Gq7oc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) in this hands-on workshop led by CNCF ambassadors and project maintainers. + +- 🎵 [Harmonizing Strategy and Engineering: Lessons Learnt in Building a Platform Plugin for Diverse Users](https://www.youtube.com/watch?v=2t_Pdiu3B1E&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from Spotify senior product manager Sri Chandrasekaran and senior engineer Kate Klymkovska. [Hey, that's me! What André said, except product vision instead of notifications. — KK] + +### Watch 'em all! + +📺 There's much more to explore and dive into. Head over to the Backstage Community YouTube channel to [watch all the Backstage talks](https://www.youtube.com/playlist?list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) from BackstageCon and KubeCon. + +## Our third Backstage ContribFest + +![Backstage ContribFest at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-contribfest.jpg) + +We talked about Backstage a ton, but we also invited attendees to help us build it. In Atlanta, we co-hosted our third [Backstage Contribfest](https://backstage.io/blog/2025/10/15/backstage-contribfest-kubecon-guide) with participants of all experience and skill levels diving into the Backstage repos to tackle issues together — and with plenty of Backstage maintainers and contributors close at hand to help out. + +A few ContribFest-fueled pull requests have already popped up in the repos: + +- fix(ui): render html in gfm mode in MarkdownContent component ([#31770](https://github.com/backstage/backstage/pull/31770)) +- catalog-react: OwnerEntityPicker case insensitive ([#31756](https://github.com/backstage/backstage/pull/31756)) +- fix: making the github urls case insensitive ([#31754](https://github.com/backstage/backstage/pull/31754)) +- fix: success announcements should be transient ([#6041](https://github.com/backstage/community-plugins/pull/6041) — ✅ merged!) + +Many thanks to all the participants and to our fellow hosts — Kurt King from Procore and Aramis Sennyey from DoorDash!🙏 + +🛠️ If you haven't yet joined our worldwide community of contributors, [there's no time like the present](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)! + +## Maintainers talk: Stable foundations, MCP innovations + +![Backstage Maintainers Track at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-maintainers.gif) + +As always, the main event for the community is the Maintainers Track session. Backstage core maintainers Ben Lambert and Patrik Oldsberg closed out the week celebrating a year of milestones — including contributions big and small from the community — while looking ahead to what's next for the framework. + +In their talk, [Backstage Celebrations: Stable Foundations and MCP Innovations](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU), they shared: + +- **Project Updates:** Adopters, plugins, and stars — and all the other ways the Backstage community continues to grow, including in the community-plugins repo. Plus, improvements to issue handling and more. + +- **Project Maturity:** With the new backend ([released as a stable 1.0 last year](https://backstage.io/docs/releases/v1.31.0/)) and new frontend ([adoption-ready as of this year](https://backstage.io/docs/releases/v1.42.0)) now both in place, the Backstage framework has modern foundations built for the future. With more emphasis on declarative integration (and less TypeScript code), Backstage is more accessible and easier to maintain — and plugins are simpler than ever to build and integrate. Make sure to watch the demo to see how quickly you can install plugins into a fresh Backstage instance. + +- **New Stuff ✨:** MCP, MCP, MCP, and MCP: What happens when you combine your AI tools with your engineering source of truth? Learn about Backstage + MCP, the latest improvements to the Backstage Actions Registry, authorization via new dynamic client registration and CLI authentication, and a live demo. Plus, all the latest on the framework's new design system, [Backstage UI](https://ui.backstage.io) (BUI). + +- **Roadmap:** Looking ahead to what's next for the New Frontend System, the Backstage UI rollout, the plugin ecosystem, MCP, CLI, and the release process. + +📺 Watch the entire [Backstage maintainers talk](https://www.youtube.com/watch?v=evmmr-uxNsc&list=PL8iP9yIjU0Q33vpSaBlAvIhgDb-9smXUU) to dig into the past, present, and future of the framework, including live demos showcasing the newest features and capabilities. + +## See you in 🌷Amsterdam! + +![Cloud native puppies improving developer experience at KubeCon + CloudNativeCon NA 2025!](assets/2025-11-25/kcon25-atl-puppies.gif) +_Cloud native puppies improving developer experience in Atlanta. 📸 [CNCF](https://www.flickr.com/photos/143247548@N03/albums/72177720330018728/)_ + +And that's a wrap for Atlanta! We say goodbye to the home of 🎤 trap music and get ready to say hello to a [stroopwafel](https://en.wikipedia.org/wiki/Stroopwafel) fika 🧇. + +See you next year at [BackstageCon](https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/co-located-events/backstagecon/) and [KubeCon + CloudNativeCon Europe 2026](https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/) in Amsterdam! diff --git a/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png b/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png index 23ed0309c1..cf09601b23 100644 Binary files a/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png and b/microsite/blog/assets/2025-10-15/backstage-contribfest-kubecon-guide-header.png differ diff --git a/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png b/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png new file mode 100644 index 0000000000..ff2010776f Binary files /dev/null and b/microsite/blog/assets/2025-11-25/backstagecon-kubecon-na-2025-header.png differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg b/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg new file mode 100644 index 0000000000..ae3414f104 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-contribfest.jpg differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif new file mode 100644 index 0000000000..ad71bc1123 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-maintainers.gif differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif new file mode 100644 index 0000000000..462afd87cb Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-puppies.gif differ diff --git a/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif b/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif new file mode 100644 index 0000000000..a7e5767441 Binary files /dev/null and b/microsite/blog/assets/2025-11-25/kcon25-atl-slideshow.gif differ diff --git a/microsite/data/plugins/announcements.yaml b/microsite/data/plugins/announcements.yaml index a9c66d6203..f24965f10a 100644 --- a/microsite/data/plugins/announcements.yaml +++ b/microsite/data/plugins/announcements.yaml @@ -1,7 +1,7 @@ --- title: Announcements -author: procore-oss -authorUrl: https://github.com/procore-oss +author: kurtaking +authorUrl: https://github.com/kurtaking category: Discovery description: Write and share announcements within Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/announcements/ diff --git a/microsite/data/plugins/conviso-platform.yaml b/microsite/data/plugins/conviso-platform.yaml new file mode 100644 index 0000000000..7017f74a3f --- /dev/null +++ b/microsite/data/plugins/conviso-platform.yaml @@ -0,0 +1,10 @@ +--- +title: Conviso Platform +author: Conviso Application Security +authorUrl: https://convisoappsec.com +category: Security +description: Import your Backstage catalog entities as security assets in Conviso Platform. +documentation: https://github.com/convisoappsec/backstage-plugin-conviso/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/convisoappsec/backstage-plugin-conviso/main/assets/convisoappsec_logo.png +npmPackageName: 'backstage-plugin-conviso' +addedDate: '2025-11-14' diff --git a/microsite/data/plugins/stackoverflow-teams.yaml b/microsite/data/plugins/stackoverflow-teams.yaml index c2fc1f5480..262a6e9c69 100644 --- a/microsite/data/plugins/stackoverflow-teams.yaml +++ b/microsite/data/plugins/stackoverflow-teams.yaml @@ -6,5 +6,5 @@ category: Discovery description: Provide seamless access to Stack Overflow Teams most relevant data, allowing you to display the top users, top tags, and top questions directly within Backstage. It also allows to securely create SO Teams questions from Backstage. documentation: https://stackoverflowteams.help/en/articles/9692515-backstage-io-integration iconUrl: /img/stack-overflow-logo.svg -npmPackageName: 'backstage-plugin-stack-overflow-teams' +npmPackageName: '@stackoverflow/backstage-plugin-stack-overflow-teams' addedDate: '2025-06-10' diff --git a/microsite/data/plugins/targetboard.yaml b/microsite/data/plugins/targetboard.yaml new file mode 100644 index 0000000000..2cac367eec --- /dev/null +++ b/microsite/data/plugins/targetboard.yaml @@ -0,0 +1,10 @@ +--- +title: TargetBoard +author: TargetBoard +authorUrl: https://www.targetboard.ai +category: Metrics +description: Embed TargetBoard dashboards and metric cards inside Backstage to visualize real-time engineering performance, KPIs, and delivery metrics. +documentation: https://github.com/targetboard/backstage-plugin/blob/main/README.md +iconUrl: https://app.targetboard.ai/assets/TargetBoard-Backstage-logo.svg +npmPackageName: '@targetboard/backstage-plugin' +addedDate: '2025-11-02' diff --git a/microsite/data/plugins/template-designer.yaml b/microsite/data/plugins/template-designer.yaml new file mode 100644 index 0000000000..7994a05430 --- /dev/null +++ b/microsite/data/plugins/template-designer.yaml @@ -0,0 +1,9 @@ +--- +title: Template Designer +author: tduniec +authorUrl: https://github.com/tduniec +category: Productivity +description: Template Designer turns blank Backstage YAML into a storyboard-like canvas, guiding anyone through drag-and-drop scaffolder authoring before ever touching code. Rally non-experts, broadcast best practices, and accelerate template launches directly inside Backstage. +documentation: https://github.com/tduniec/template-designer-plugin/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/tduniec/template-designer-plugin/main/img/logo/templateDesignerLogo.png +addedDate: 2025-11-19 diff --git a/microsite/data/plugins/usage-statistics.yaml b/microsite/data/plugins/usage-statistics.yaml new file mode 100644 index 0000000000..d32fb54ac4 --- /dev/null +++ b/microsite/data/plugins/usage-statistics.yaml @@ -0,0 +1,9 @@ +--- +title: Usage Statistics +author: CodeVerse-GP +authorUrl: https://github.com/CodeVerse-GP +category: Monitoring +description: Shows usage statistics for scaffolder templates +documentation: https://github.com/CodeVerse-GP/usage-statistics/blob/main/README.md +npmPackageName: '@codeverse-gp/plugin-usage-statistics' +addedDate: '2025-11-05' diff --git a/microsite/package.json b/microsite/package.json index a042a23286..a3430545f4 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -44,11 +44,10 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", "@docusaurus/tsconfig": "^3.1.1", - "@types/luxon": "^3.0.0", "@types/webpack-env": "^1.18.0", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "prettier": "^2.6.2", - "typescript": "~5.2.0", + "typescript": "~5.7.0", "yaml-loader": "^0.8.0" } } diff --git a/microsite/static/img/cncf-white.svg b/microsite/static/img/cncf-white.svg index c5e3b2af9a..652249b8d8 100644 --- a/microsite/static/img/cncf-white.svg +++ b/microsite/static/img/cncf-white.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 9040e75d86..2f6e723889 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2460,14 +2460,14 @@ __metadata: linkType: hard "@mdx-js/react@npm:^3.0.0": - version: 3.1.0 - resolution: "@mdx-js/react@npm:3.1.0" + version: 3.1.1 + resolution: "@mdx-js/react@npm:3.1.1" dependencies: "@types/mdx": "npm:^2.0.0" peerDependencies: "@types/react": ">=16" react: ">=16" - checksum: 10/cf89d6392c76091622fb647f205e1ab5cbdf5edd4401dde7092138cefc9fbb6d61428aa63557de0bccca3695d5a8854dd4a93b34a27cb8e27369da7eaeaa3e73 + checksum: 10/52a740e2f37761694fa94d4704b7825084b4055616a95c8b8f4c1676190d399ddc5cdbb399ffc45b550beecd30497a7224c2e5b05bf43ecb668c7473641037d1 languageName: node linkType: hard @@ -2961,92 +2961,92 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-arm64@npm:1.11.24" +"@swc/core-darwin-arm64@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-darwin-arm64@npm:1.15.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-x64@npm:1.11.24" +"@swc/core-darwin-x64@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-darwin-x64@npm:1.15.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" +"@swc/core-linux-arm-gnueabihf@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.3" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" +"@swc/core-linux-arm64-gnu@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" +"@swc/core-linux-arm64-musl@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" +"@swc/core-linux-x64-gnu@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-musl@npm:1.11.24" +"@swc/core-linux-x64-musl@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-linux-x64-musl@npm:1.15.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" +"@swc/core-win32-arm64-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" +"@swc/core-win32-ia32-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" +"@swc/core-win32-x64-msvc@npm:1.15.3": + version: 1.15.3 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.11.24 - resolution: "@swc/core@npm:1.11.24" + version: 1.15.3 + resolution: "@swc/core@npm:1.15.3" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.24" - "@swc/core-darwin-x64": "npm:1.11.24" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.24" - "@swc/core-linux-arm64-gnu": "npm:1.11.24" - "@swc/core-linux-arm64-musl": "npm:1.11.24" - "@swc/core-linux-x64-gnu": "npm:1.11.24" - "@swc/core-linux-x64-musl": "npm:1.11.24" - "@swc/core-win32-arm64-msvc": "npm:1.11.24" - "@swc/core-win32-ia32-msvc": "npm:1.11.24" - "@swc/core-win32-x64-msvc": "npm:1.11.24" + "@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/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.21" + "@swc/types": "npm:^0.1.25" peerDependencies: "@swc/helpers": ">=0.5.17" dependenciesMeta: @@ -3073,7 +3073,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 + checksum: 10/280330d82328818138ed64fdcf9ea9abde6b6f16eca65a9d4db27dde06a8dfffd2649f3447d2243387277513c7430fa4142cafcfd64e943d682ce6a713cb8c2d languageName: node linkType: hard @@ -3084,12 +3084,12 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.21": - version: 0.1.21 - resolution: "@swc/types@npm:0.1.21" +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" dependencies: "@swc/counter": "npm:^0.1.3" - checksum: 10/6554bf5c78519f49099a2ba448d170191a14b1c7a35df848f10ee4d6c03ecd681e5213884905187de1d1d221589ec8b5cb77f477d099dc1627c3ec9d7f2fcdb0 + checksum: 10/f6741450224892d12df43e5ca7f3cc0287df644dcd672626eb0cc2a3a8e3e875f4b29eb11336f37c7240cf6e010ba59eb3a79f4fb8bee5cbd168dfc1326ff369 languageName: node linkType: hard @@ -3328,13 +3328,6 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:^3.0.0": - version: 3.4.2 - resolution: "@types/luxon@npm:3.4.2" - checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d - languageName: node - linkType: hard - "@types/mdast@npm:^3.0.0": version: 3.0.15 resolution: "@types/mdast@npm:3.0.15" @@ -4282,14 +4275,13 @@ __metadata: "@docusaurus/types": "npm:^3.1.1" "@mdx-js/react": "npm:^3.0.0" "@swc/core": "npm:^1.3.46" - "@types/luxon": "npm:^3.0.0" "@types/webpack-env": "npm:^1.18.0" clsx: "npm:^2.0.0" docusaurus-plugin-openapi-docs: "npm:^4.3.0" docusaurus-plugin-sass: "npm:^0.2.3" docusaurus-pushfeedback: "npm:^1.0.0" docusaurus-theme-openapi-docs: "npm:^4.3.0" - js-yaml: "npm:^4.1.0" + js-yaml: "npm:^4.1.1" luxon: "npm:^3.0.0" prettier: "npm:^2.6.2" prism-react-renderer: "npm:^2.1.0" @@ -4297,7 +4289,7 @@ __metadata: react-dom: "npm:^18.0.2" sass: "npm:^1.57.1" swc-loader: "npm:^0.2.3" - typescript: "npm:~5.2.0" + typescript: "npm:~5.7.0" yaml-loader: "npm:^0.8.0" languageName: unknown linkType: soft @@ -4358,23 +4350,23 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.3": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" dependencies: - bytes: "npm:3.1.2" + bytes: "npm:~3.1.2" content-type: "npm:~1.0.5" debug: "npm:2.6.9" depd: "npm:2.0.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:2.5.2" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca + unpipe: "npm:~1.0.0" + checksum: 10/ff67e28d3f426707be8697a75fdf8d564dc50c341b41f054264d8ab6e2924e519c7ce8acc9d0de05328fdc41e1d9f3f200aec9c1cfb1867d6b676a410d97c689 languageName: node linkType: hard @@ -4591,7 +4583,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 @@ -5223,7 +5215,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4": +"content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5246,17 +5238,17 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.6": - version: 1.0.6 - resolution: "cookie-signature@npm:1.0.6" - checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 languageName: node linkType: hard -"cookie@npm:0.6.0": - version: 0.6.0 - resolution: "cookie@npm:0.6.0" - checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 +"cookie@npm:~0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f languageName: node linkType: hard @@ -5772,7 +5764,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -5803,7 +5795,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0": +"destroy@npm:1.2.0, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -5914,8 +5906,8 @@ __metadata: linkType: hard "docusaurus-plugin-openapi-docs@npm:^4.3.0": - version: 4.3.7 - resolution: "docusaurus-plugin-openapi-docs@npm:4.3.7" + version: 4.5.1 + resolution: "docusaurus-plugin-openapi-docs@npm:4.5.1" dependencies: "@apidevtools/json-schema-ref-parser": "npm:^11.5.4" "@redocly/openapi-core": "npm:^1.10.5" @@ -5937,7 +5929,7 @@ __metadata: "@docusaurus/utils": ^3.5.0 "@docusaurus/utils-validation": ^3.5.0 react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/ad011358b13fd5e574e33ad3e1b2bb002226e2dcab72e6a8c98c4ad5436a81e28815afe6f14252a48bb52f0c088b03dfd93a5955a8a6883fbda794bebf7d1792 + checksum: 10/496ce49d494f06803a61eaa6c6810782668269574c12a1a55315742974884a7ffd6e2c75875d8fd9909611bdccaa4de42419e0debbf47def0db6974617b9e5ba languageName: node linkType: hard @@ -6541,41 +6533,41 @@ __metadata: linkType: hard "express@npm:^4.17.3": - version: 4.21.0 - resolution: "express@npm:4.21.0" + version: 4.22.1 + resolution: "express@npm:4.22.1" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.6.0" - cookie-signature: "npm:1.0.6" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" + path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" + qs: "npm:~6.14.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/3b1ee5bc5b1bd996f688702519cebc9b63a24e506965f6e1773268238cfa2c24ffdb38cc3fcb4fde66f77de1c0bebd9ee058dad06bb9c6f084b525f3c09164d3 + checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470 languageName: node linkType: hard @@ -6723,18 +6715,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" dependencies: debug: "npm:2.6.9" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" + on-finished: "npm:~2.4.1" parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" + statuses: "npm:~2.0.2" unpipe: "npm:~1.0.0" - checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b + checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed languageName: node linkType: hard @@ -6872,7 +6864,7 @@ __metadata: languageName: node linkType: hard -"fresh@npm:0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 @@ -7751,6 +7743,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10/9fe31bc0edf36566c87048aed1d3d0cbe03552564adc3541626a0613f542d753fbcb13bdfcec0a3a530dbe1714bb566c89d46244616b66bddd26ac413b06a207 + languageName: node + linkType: hard + "http-parser-js@npm:>=0.5.1": version: 0.5.8 resolution: "http-parser-js@npm:0.5.8" @@ -7865,15 +7870,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 - languageName: node - linkType: hard - "iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -7883,6 +7879,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:~0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 + languageName: node + linkType: hard + "icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": version: 5.1.0 resolution: "icss-utils@npm:5.1.0" @@ -8508,7 +8513,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0": +"js-yaml@npm:4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -8520,14 +8525,25 @@ __metadata: linkType: hard "js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: "npm:^1.0.7" esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 + checksum: 10/172e0b6007b0bf0fc8d2469c94424f7dd765c64a047d2b790831fecef2204a4054eabf4d911eb73ab8c9a3256ab8ba1ee8d655b789bf24bf059c772acc2075a1 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 languageName: node linkType: hard @@ -8893,9 +8909,9 @@ __metadata: linkType: hard "luxon@npm:^3.0.0": - version: 3.5.0 - resolution: "luxon@npm:3.5.0" - checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be languageName: node linkType: hard @@ -10620,9 +10636,9 @@ __metadata: linkType: hard "node-forge@npm:^1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d + version: 1.3.2 + resolution: "node-forge@npm:1.3.2" + checksum: 10/dcc54aaffe0cf52367214a20c0032aa9b209d9095dd14526504f1972d1900a07e96046b3684cb0c8d0cc3d48744dd18e02b7b447ab28fac615ffb850beeabf18 languageName: node linkType: hard @@ -10898,7 +10914,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -11276,13 +11292,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 - languageName: node - linkType: hard - "path-to-regexp@npm:2.2.1": version: 2.2.1 resolution: "path-to-regexp@npm:2.2.1" @@ -11299,6 +11308,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:~0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -12038,16 +12054,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" - dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 - languageName: node - linkType: hard - -"qs@npm:^6.12.3": +"qs@npm:^6.12.3, qs@npm:~6.14.0": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -12119,15 +12126,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.4.24" - unpipe: "npm:1.0.0" - checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10/f35759fe5a6548e7c529121ead1de4dd163f899749a5896c42e278479df2d9d7f98b5bb17312737c03617765e5a1433e586f717616e5cfbebc13b4738b820601 languageName: node linkType: hard @@ -13048,8 +13055,8 @@ __metadata: linkType: hard "sass@npm:^1.57.1, sass@npm:^1.80.4": - version: 1.83.4 - resolution: "sass@npm:1.83.4" + version: 1.94.2 + resolution: "sass@npm:1.94.2" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -13060,7 +13067,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10/9a7d1c6be1a9e711a1c561d189b9816aa7715f6d0ec0b2ec181f64163788d0caaf4741924eeadce558720b58b1de0e9b21b9dae6a0d14489c4d2a142d3f3b12e + checksum: 10/e60c214ea93677740c9ddfad55c77fd433255bbfdd9faba137acf1215bed5ba6ad9d83efea81feb87a89283931d01f0435227e3fff37c65c263e0ee05f885328 languageName: node linkType: hard @@ -13188,6 +13195,27 @@ __metadata: languageName: node linkType: hard +"send@npm:~0.19.0": + version: 0.19.1 + resolution: "send@npm:0.19.1" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/360bf50a839c7bbc181f67c3a0f3424a7ad8016dfebcd9eb90891f4b762b4377da14414c32250d67b53872e884171c27469110626f6c22765caa7c38c207ee1d + languageName: node + linkType: hard + "serialize-javascript@npm:^6.0.0, serialize-javascript@npm:^6.0.1": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -13228,7 +13256,7 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": +"serve-static@npm:~1.16.2": version: 1.16.2 resolution: "serve-static@npm:1.16.2" dependencies: @@ -13275,7 +13303,7 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e @@ -13438,7 +13466,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -13685,6 +13713,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + "std-env@npm:^3.0.1": version: 3.3.1 resolution: "std-env@npm:3.3.1" @@ -14108,7 +14143,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -14215,23 +14250,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.2.0": - version: 5.2.2 - resolution: "typescript@npm:5.2.2" +"typescript@npm:~5.7.0": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/d65e50eb849bd21ff8677e5b9447f9c6e74777e346afd67754934264dcbf4bd59e7d2473f6062d9a015d66bd573311166357e3eb07fea0b52859cf9bb2b58555 + checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.2.0#optional!builtin": - version: 5.2.2 - resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin::version=5.2.2&hash=f3b441" +"typescript@patch:typescript@npm%3A~5.7.0#optional!builtin": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f79cc2ba802c94c2b78dbb00d767a10adb67368ae764709737dc277273ec148aa4558033a03ce901406b35fddf4eac46dabc94a1e1d12d2587e2b9cfe5707b4a + checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 languageName: node linkType: hard @@ -14459,7 +14494,7 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 diff --git a/package.json b/package.json index 614ec28f35..a7ef0c8056 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.45.0-next.0", + "version": "1.46.0-next.1", "backstage": { "cli": { "new": { @@ -45,7 +45,9 @@ "lint:docs": "node ./scripts/check-docs-quality", "lint:peer-deps": "backstage-repo-tools peer-deps", "lint:type-deps": "backstage-repo-tools type-deps", + "mui-to-bui": "node scripts/mui-to-bui/backstage-migration-analytics.js", "new": "backstage-cli new", + "patch-pr": "node .patches/create-pr-patch.js", "prepare": "husky", "prettier:check": "prettier --check .", "prettier:fix": "prettier --write .", @@ -64,8 +66,8 @@ "test": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test", "test:all": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test --coverage", "test:e2e": "NODE_OPTIONS=--no-node-snapshot playwright test", - "tsc": "tsc", - "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false" + "tsc": "NODE_OPTIONS='--max-old-space-size=8192' tsc", + "tsc:full": "backstage-cli repo clean && NODE_OPTIONS='--max-old-space-size=8192' tsc --skipLibCheck false --incremental false" }, "lint-staged": { "*.{js,jsx,ts,tsx,mjs,cjs}": [ @@ -106,6 +108,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", + "GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0", "ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "ast-types@^0.14.1": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "ast-types@npm:0.14.2": "patch:ast-types@npm%3A0.16.1#./.yarn/patches/ast-types-npm-0.16.1-43c4ac4b0d.patch", @@ -115,7 +118,6 @@ "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", - "GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0", "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": { @@ -144,7 +146,7 @@ "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", "@types/memjs": "^1.3.3", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/webpack": "^5.28.0", "array-to-table": "^1.0.1", "command-exists": "^1.2.9", @@ -166,13 +168,14 @@ "sloc": "^0.3.1", "sort-package-json": "^2.8.0", "storybook": "^9.1.7", + "ts-morph": "^24.0.0", "typedoc": "^0.28.0", "typescript": "~5.7.0", "vite": "^7.1.5" }, "packageManager": "yarn@4.8.1", "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "madge": { "fileExtensions": [ diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index fbbbdf6569..ee64c2209e 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/app-defaults +## 1.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 1.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + ## 1.7.2-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 8cd48112b2..ec08c04929 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.7.2-next.0", + "version": "1.7.3-next.0", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 74ba940174..da46f90c56 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,21 @@ # app-next-example-plugin +## 0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-components@0.18.4-next.0 + +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-components@0.18.3 + ## 0.0.29-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 5415d793b3..f7d113827d 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.29-next.0", + "version": "0.0.30-next.0", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next-example-plugin/report.api.md b/packages/app-next-example-plugin/report.api.md index 12346d908c..bfcafbe2d2 100644 --- a/packages/app-next-example-plugin/report.api.md +++ b/packages/app-next-example-plugin/report.api.md @@ -5,9 +5,9 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -16,7 +16,7 @@ const examplePlugin: OverridableFrontendPlugin< {}, {}, { - 'page:example': ExtensionDefinition<{ + 'page:example': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; config: { diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index ffa195a96c..9e29cd2b2e 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,150 @@ # example-app-next +## 0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## 0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-app-visualizer@0.1.26-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @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.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-compat-api@0.5.4 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-app-visualizer@0.1.25 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-auth@0.1.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/frontend-defaults@0.3.3 + - @backstage/plugin-app@0.3.2 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## 0.0.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-signals@0.0.25-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-search@1.5.0-next.1 + - @backstage/plugin-app-visualizer@0.1.25-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + - @backstage/plugin-catalog-graph@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.2-next.1 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/core-compat-api@0.5.4-next.0 + - @backstage/plugin-api-docs@0.13.1-next.1 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + +## 0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + ## 0.0.29-next.0 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index dcaddfb9bf..1121cfc0d5 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -21,6 +21,11 @@ app: ownerEntityRefs: [cubic-belugas] extensions: + # set availableLanguages example + - api:app/app-language: + config: + availableLanguages: ['en', 'es', 'fr', 'de', 'ja'] + defaultLanguage: 'en' - entity-card:org/members-list: config: showAggregateMembersToggle: true diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 3b6a78aa27..2a3b5dbf8a 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.29-next.0", + "version": "0.0.30-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html index 63ab0bec0c..74387edea3 100644 --- a/packages/app-next/public/index.html +++ b/packages/app-next/public/index.html @@ -4,6 +4,7 @@ + { - return compatWrapper( - - - } to="/search"> - - - - }> - - {items.map((item, index) => ( - - ))} - - - - - - } - to="/settings" - > - - - - - , - ); - }, + component: ({ items }) => ( + + + } to="/search"> + + + + }> + + {items.map((item, index) => ( + + ))} + + + + + + } + to="/settings" + > + + + + + + ), }, }), ], diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index f524bfc95d..82e800c5ef 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,140 @@ # example-app +## 0.2.116-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.1 + +## 0.2.116-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graph@0.5.4-next.0 + - @backstage/ui@0.9.1-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.0 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + - @backstage/plugin-scaffolder@1.34.4-next.0 + - @backstage/plugin-api-docs@0.13.2-next.0 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/plugin-search@1.5.1-next.0 + - @backstage/plugin-home@0.8.15-next.0 + - @backstage/plugin-org@0.6.47-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-mui-to-bui@0.2.2-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + - @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.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-search-common@1.2.21 + +## 0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.13.1 + - @backstage/ui@0.9.0 + - @backstage/plugin-org@0.6.46 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-signals@0.0.25 + - @backstage/frontend-app-api@0.13.2 + - @backstage/plugin-search-react@1.10.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/plugin-search@1.5.0 + - @backstage/plugin-home@0.8.14 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30 + - @backstage/plugin-scaffolder-react@1.19.3 + - @backstage/plugin-catalog-graph@0.5.3 + - @backstage/core-components@0.18.3 + - @backstage/plugin-user-settings@0.8.29 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-catalog-import@0.13.7 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/plugin-scaffolder@1.34.3 + - @backstage/plugin-catalog-unprocessed-entities@0.2.23 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mui-to-bui@0.2.1 + - @backstage/plugin-devtools@0.1.33 + - @backstage/plugin-kubernetes@0.12.13 + - @backstage/plugin-notifications@0.5.11 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/plugin-auth-react@0.1.21 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.31 + - @backstage/plugin-search-common@1.2.21 + +## 0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-signals@0.0.25-next.1 + - @backstage/plugin-search-react@1.10.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/plugin-search@1.5.0-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.30-next.1 + - @backstage/plugin-catalog-react@1.21.3-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + - @backstage/plugin-catalog-graph@0.5.3-next.1 + - @backstage/plugin-scaffolder@1.34.3-next.0 + - @backstage/plugin-api-docs@0.13.1-next.1 + - @backstage/plugin-catalog-import@0.13.7-next.0 + - @backstage/plugin-org@0.6.46-next.0 + - @backstage/plugin-scaffolder-react@1.19.3-next.0 + - @backstage/plugin-user-settings@0.8.29-next.0 + +## 0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + - @backstage/plugin-mui-to-bui@0.2.1-next.1 + ## 0.2.115-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 6766f1fca1..5515e2a9c7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.115-next.0", + "version": "0.2.116-next.1", "backstage": { "role": "frontend" }, @@ -95,7 +95,7 @@ "@types/react": "*", "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", - "axios": "^1.12.0", + "axios": "^1.13.0", "cross-env": "^7.0.0", "msw": "^1.0.0" }, diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 9a8267e98e..9b67673b2e 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -4,6 +4,7 @@ + , ): void; // (undocumented) - start(): Promise; + start(): Promise<{ + result: BackendStartupResult; + }>; // (undocumented) stop(): Promise; } +// @public +export class BackendStartupError extends CustomErrorBase { + constructor(startupResult: BackendStartupResult); + // (undocumented) + name: 'BackendStartupError'; + // (undocumented) + get result(): BackendStartupResult; +} + +// @public +export interface BackendStartupResult { + beginAt: Date; + outcome: 'success' | 'failure'; + plugins: PluginStartupResult[]; + resultAt: Date; +} + // @public (undocumented) export function createSpecializedBackend( options: CreateSpecializedBackendOptions, @@ -32,4 +52,25 @@ export interface CreateSpecializedBackendOptions { // (undocumented) defaultServiceFactories: ServiceFactory[]; } + +// @public +export interface ModuleStartupResult { + failure?: { + error: Error; + allowed: boolean; + }; + moduleId: string; + resultAt: Date; +} + +// @public +export interface PluginStartupResult { + failure?: { + error: Error; + allowed: boolean; + }; + modules: ModuleStartupResult[]; + pluginId: string; + resultAt: Date; +} ``` diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 9e8a6579fd..1c0bf5a0ef 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -22,10 +22,11 @@ import { createExtensionPoint, createBackendFeatureLoader, ServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; import { mockServices } from '@backstage/backend-test-utils'; +import { BackendStartupError } from './BackendStartupError'; const baseFactories = [ mockServices.rootLifecycle.factory(), @@ -57,6 +58,42 @@ const testPlugin = createBackendPlugin({ }); describe('BackendInitializer', () => { + it('should only load modules if the plugin is present', async () => { + let loadedModule = false; + const backend1 = new BackendInitializer(baseFactories); + const testModule = createBackendModule({ + pluginId: 'test', + moduleId: 'producer', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + loadedModule = true; + }, + }); + }, + }); + await backend1.add(testModule); + await backend1.start(); + expect(loadedModule).toBe(false); + + const backend2 = new BackendInitializer(baseFactories); + await backend2.add(testModule); + await backend2.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + await backend2.start(); + expect(loadedModule).toBe(true); + }); + it('should initialize root scoped services', async () => { const ref1 = createServiceRef<{ x: number }>({ id: '1', @@ -770,49 +807,6 @@ describe('BackendInitializer', () => { ); }); - it('should forward errors when multiple plugins fail to start', async () => { - const init = new BackendInitializer([]); - init.add( - createBackendPlugin({ - pluginId: 'test-1', - register(reg) { - reg.registerInit({ - deps: {}, - async init() { - throw new Error('NOPE A'); - }, - }); - }, - }), - ); - init.add( - createBackendPlugin({ - pluginId: 'test-2', - register(reg) { - reg.registerInit({ - deps: {}, - async init() { - throw new Error('NOPE B'); - }, - }); - }, - }), - ); - const result = init.start(); - - await expect(result).rejects.toThrow('Backend startup failed'); - await expect(result).rejects.toMatchObject({ - errors: [ - expect.objectContaining({ - message: "Plugin 'test-1' startup failed; caused by Error: NOPE A", - }), - expect.objectContaining({ - message: "Plugin 'test-2' startup failed; caused by Error: NOPE B", - }), - ], - }); - }); - it('should forward errors when modules fail to start', async () => { const init = new BackendInitializer([]); init.add(testPlugin); @@ -1074,7 +1068,7 @@ describe('BackendInitializer', () => { }); it('should properly add plugins + modules to the instance metadata service', async () => { - expect.assertions(2); + expect.assertions(1); const backend = new BackendInitializer(baseFactories); const plugin = createBackendPlugin({ pluginId: 'test', @@ -1090,31 +1084,25 @@ describe('BackendInitializer', () => { register(reg) { reg.registerInit({ deps: { - instanceMetadata: instanceMetadataServiceRef, + instanceMetadata: coreServices.rootInstanceMetadata, }, async init({ instanceMetadata }) { - expect(instanceMetadata.getInstalledFeatures()).toEqual([ + await expect( + instanceMetadata.getInstalledPlugins(), + ).resolves.toEqual([ { pluginId: 'test', - type: 'plugin', - }, - { - pluginId: 'test', - moduleId: 'test', - type: 'module', + modules: [ + { + moduleId: 'test', + }, + ], }, { pluginId: 'instance-metadata', - type: 'plugin', + modules: [], }, ]); - expect(instanceMetadata.getInstalledFeatures().map(String)).toEqual( - [ - 'plugin{pluginId=test}', - 'module{moduleId=test,pluginId=test}', - 'plugin{pluginId=instance-metadata}', - ], - ); }, }); }, @@ -1135,6 +1123,67 @@ describe('BackendInitializer', () => { await backend.start(); }); + it('should ignore modules that do not have a matching plugin', async () => { + expect.assertions(1); + const backend = new BackendInitializer(baseFactories); + const instanceMetadataPlugin = createBackendPlugin({ + pluginId: 'instance-metadata', + register(reg) { + reg.registerInit({ + deps: { + instanceMetadata: coreServices.rootInstanceMetadata, + }, + async init({ instanceMetadata }) { + await expect( + instanceMetadata.getInstalledPlugins(), + ).resolves.toEqual([ + { + pluginId: 'instance-metadata', + modules: [], + }, + ]); + }, + }); + }, + }); + const module = createBackendModule({ + pluginId: 'test', + moduleId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }); + backend.add(module); + backend.add(instanceMetadataPlugin); + await backend.start(); + }); + + it('should prevent writes to the instance metadata service', async () => { + expect.assertions(1); + const backend = new BackendInitializer(baseFactories); + const plugin = createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: { + instanceMetadata: coreServices.rootInstanceMetadata, + }, + async init({ instanceMetadata }) { + const plugins = await instanceMetadata.getInstalledPlugins(); + await expect(() => { + (plugins[0] as any).pluginId = 'foo'; + }).toThrow(/Cannot assign to read only property/); + }, + }); + }, + }); + backend.add(plugin); + await backend.start(); + }); + it('should properly wait for all modules that consume an extension point to really finish, before starting the module that provides that extension point', async () => { expect.assertions(3); const backend = new BackendInitializer(baseFactories); @@ -1198,4 +1247,594 @@ describe('BackendInitializer', () => { await backend.add(slowConsumerModule); await backend.start(); }); + + describe('startup results', () => { + it('should return successful startup result when all plugins and modules start successfully', async () => { + const init = new BackendInitializer(baseFactories); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendPlugin({ + pluginId: 'plugin2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin2', + moduleId: 'module2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins).toHaveLength(2); + expect(result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + modules: [ + { + moduleId: 'module1', + }, + ], + }); + expect(result.plugins[0].failure).toBeUndefined(); + expect(result.plugins[0].modules[0].failure).toBeUndefined(); + expect(result.plugins[1]).toMatchObject({ + pluginId: 'plugin2', + modules: [ + { + moduleId: 'module2', + }, + ], + }); + expect(result.plugins[1].failure).toBeUndefined(); + expect(result.plugins[1].modules[0].failure).toBeUndefined(); + }); + + it('should throw BackendStartupError with results when plugin fails to start', async () => { + const init = new BackendInitializer(baseFactories); + const error = new Error('Plugin failed'); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error; + }, + }); + }, + }), + ); + init.add( + createBackendPlugin({ + pluginId: 'plugin2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const err = await init.start().then( + () => { + throw new Error('Expected BackendStartupError to be thrown'); + }, + (e: BackendStartupError) => e, + ); + + expect(err).toBeInstanceOf(BackendStartupError); + expect(err?.result.plugins).toHaveLength(2); + expect(err?.result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + failure: { error, allowed: false }, + }); + expect(err?.result.plugins[1]).toMatchObject({ + pluginId: 'plugin2', + }); + expect(err?.result.plugins[1].failure).toBeUndefined(); + }); + + it('should throw BackendStartupError with results when module fails to start', async () => { + const init = new BackendInitializer(baseFactories); + const error = new Error('Module failed'); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error; + }, + }); + }, + }), + ); + + const err = await init.start().then( + () => { + throw new Error('Expected BackendStartupError to be thrown'); + }, + (e: BackendStartupError) => e, + ); + + expect(err).toBeInstanceOf(BackendStartupError); + expect(err?.result.plugins).toHaveLength(1); + expect(err?.result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + modules: [ + { + moduleId: 'module1', + failure: { error, allowed: false }, + }, + ], + }); + expect(err?.result.plugins[0].failure).toBeUndefined(); + }); + + it('should throw BackendStartupError with results when multiple plugins fail', async () => { + const init = new BackendInitializer(baseFactories); + const error1 = new Error('Plugin1 failed'); + const error2 = new Error('Plugin2 failed'); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error1; + }, + }); + }, + }), + ); + init.add( + createBackendPlugin({ + pluginId: 'plugin2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error2; + }, + }); + }, + }), + ); + + const err = await init.start().then( + () => { + throw new Error('Expected BackendStartupError to be thrown'); + }, + (e: BackendStartupError) => e, + ); + + expect(err).toBeInstanceOf(BackendStartupError); + expect(err?.result.plugins).toHaveLength(2); + expect(err?.result.plugins[0].failure?.error).toBe(error1); + expect(err?.result.plugins[1].failure?.error).toBe(error2); + }); + + it('should return results with failure status when plugin boot failure is permitted', async () => { + const error = new Error('Plugin failed'); + const init = new BackendInitializer([ + ...baseFactories, + mockServices.rootConfig.factory({ + data: { + backend: { + startup: { + plugins: { plugin1: { onPluginBootFailure: 'continue' } }, + }, + }, + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error; + }, + }); + }, + }), + ); + init.add( + createBackendPlugin({ + pluginId: 'plugin2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins).toHaveLength(2); + expect(result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + failure: { error, allowed: true }, + }); + expect(result.plugins[1]).toMatchObject({ + pluginId: 'plugin2', + }); + expect(result.plugins[1].failure).toBeUndefined(); + }); + + it('should return results with failure status when module boot failure is permitted', async () => { + const error = new Error('Module failed'); + const init = new BackendInitializer([ + ...baseFactories, + mockServices.rootConfig.factory({ + data: { + backend: { + startup: { + plugins: { + plugin1: { + modules: { + module1: { onPluginModuleBootFailure: 'continue' }, + }, + }, + }, + }, + }, + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error; + }, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins).toHaveLength(1); + expect(result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + modules: [ + { + moduleId: 'module1', + failure: { error, allowed: true }, + }, + { + moduleId: 'module2', + }, + ], + }); + expect(result.plugins[0].failure).toBeUndefined(); + expect(result.plugins[0].modules[1].failure).toBeUndefined(); + }); + + it('should include all module results even when some modules fail', async () => { + const error1 = new Error('Module1 failed'); + const error2 = new Error('Module2 failed'); + const init = new BackendInitializer([ + ...baseFactories, + mockServices.rootConfig.factory({ + data: { + backend: { + startup: { + plugins: { + plugin1: { + modules: { + module1: { onPluginModuleBootFailure: 'continue' }, + module2: { onPluginModuleBootFailure: 'continue' }, + }, + }, + }, + }, + }, + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error1; + }, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module2', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error2; + }, + }); + }, + }), + ); + init.add( + createBackendModule({ + pluginId: 'plugin1', + moduleId: 'module3', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins[0].modules).toHaveLength(3); + expect(result.plugins[0].modules[0]).toMatchObject({ + moduleId: 'module1', + failure: { error: error1, allowed: true }, + }); + expect(result.plugins[0].modules[1]).toMatchObject({ + moduleId: 'module2', + failure: { error: error2, allowed: true }, + }); + expect(result.plugins[0].modules[2]).toMatchObject({ + moduleId: 'module3', + }); + expect(result.plugins[0].modules[2].failure).toBeUndefined(); + }); + + it('should return results for plugins without modules', async () => { + const init = new BackendInitializer(baseFactories); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins).toHaveLength(1); + expect(result.plugins[0]).toMatchObject({ + pluginId: 'plugin1', + modules: [], + }); + expect(result.plugins[0].failure).toBeUndefined(); + }); + + it('should include error information in BackendStartupError', async () => { + const error = new Error('Test error'); + const init = new BackendInitializer(baseFactories); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw error; + }, + }); + }, + }), + ); + + const err = await init.start().then( + () => { + throw new Error('Expected BackendStartupError to be thrown'); + }, + (e: BackendStartupError) => e, + ); + + expect(err).toBeInstanceOf(BackendStartupError); + expect(err?.message).toBe( + "Backend startup failed due to the following errors:\n Plugin 'plugin1' startup failed; caused by Error: Test error", + ); + expect(err?.result).toBeDefined(); + expect(err?.result.plugins[0].failure?.error).toBe(error); + }); + + it('should handle plugin scoped service factory failures', async () => { + const serviceFactoryError = new Error('Service factory failed'); + const ref = createServiceRef<{ value: string }>({ + id: 'failing-service', + }); + const init = new BackendInitializer([ + ...baseFactories, + createServiceFactory({ + service: ref, + deps: {}, + factory: () => { + throw serviceFactoryError; + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: { service: ref }, + async init() {}, + }); + }, + }), + ); + + const err = await init.start().then( + () => { + throw new Error('Expected BackendStartupError to be thrown'); + }, + (e: BackendStartupError) => e, + ); + + expect(err).toBeInstanceOf(BackendStartupError); + expect(err?.result.plugins).toHaveLength(1); + expect(err?.result.plugins[0].pluginId).toBe('plugin1'); + expect(err?.result.plugins[0].failure).toBeDefined(); + expect(err?.result.plugins[0].failure?.allowed).toBe(false); + expect(err?.result.plugins[0].failure?.error.message).toContain( + "Failed to instantiate service 'failing-service'", + ); + expect(err?.result.plugins[0].failure?.error.message).toContain( + 'Service factory failed', + ); + }); + + it('should handle plugin scoped service factory failures with allowed boot failure', async () => { + const serviceFactoryError = new Error('Service factory failed'); + const ref = createServiceRef<{ value: string }>({ + id: 'failing-service', + }); + const init = new BackendInitializer([ + ...baseFactories, + mockServices.rootConfig.factory({ + data: { + backend: { + startup: { + plugins: { plugin1: { onPluginBootFailure: 'continue' } }, + }, + }, + }, + }), + createServiceFactory({ + service: ref, + deps: {}, + factory: () => { + throw serviceFactoryError; + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'plugin1', + register(reg) { + reg.registerInit({ + deps: { service: ref }, + async init() {}, + }); + }, + }), + ); + + const { result } = await init.start(); + + expect(result.plugins).toHaveLength(1); + expect(result.plugins[0].pluginId).toBe('plugin1'); + expect(result.plugins[0].failure).toBeDefined(); + expect(result.plugins[0].failure?.allowed).toBe(true); + expect(result.plugins[0].failure?.error.message).toContain( + "Failed to instantiate service 'failing-service'", + ); + expect(result.plugins[0].failure?.error.message).toContain( + 'Service factory failed', + ); + }); + }); }); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 09b22e3a7a..c86fb0b29c 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -24,7 +24,6 @@ import { RootLifecycleService, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { ServiceOrExtensionPoint } from './types'; // Direct internal import to avoid duplication // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -36,14 +35,14 @@ import type { // eslint-disable-next-line @backstage/no-relative-monorepo-imports import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types'; import { ForwardedError, ConflictError, assertError } from '@backstage/errors'; -import { - instanceMetadataServiceRef, - BackendFeatureMeta, -} from '@backstage/backend-plugin-api/alpha'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; -import { createInitializationLogger } from './createInitializationLogger'; -import { unwrapFeature } from './helpers'; +import { createInitializationResultCollector } from './createInitializationResultCollector'; +import { deepFreeze, unwrapFeature } from './helpers'; +import type { RootInstanceMetadataServicePluginInfo } from '@backstage/backend-plugin-api'; +import { BackendStartupResult } from './types'; +import { BackendStartupError } from './BackendStartupError'; +import { createAllowBootFailurePredicate } from './createAllowBootFailurePredicate'; export interface BackendRegisterInit { consumes: Set; @@ -101,61 +100,57 @@ const instanceRegistry = new (class InstanceRegistry { }; })(); -function createInstanceMetadataServiceFactory( - registrations: InternalBackendRegistrations[], +function createRootInstanceMetadataServiceFactory( + rawRegistrations: InternalBackendRegistrations[], ) { - const installedFeatures = registrations - .map(registration => { - if (registration.featureType === 'registrations') { - return registration - .getRegistrations() - .map(feature => { - if (feature.type === 'plugin') { - return Object.defineProperty( - { - type: 'plugin', - pluginId: feature.pluginId, - }, - 'toString', - { - enumerable: false, - configurable: true, - value: () => `plugin{pluginId=${feature.pluginId}}`, - }, - ); - } else if (feature.type === 'module') { - return Object.defineProperty( - { - type: 'module', - pluginId: feature.pluginId, - moduleId: feature.moduleId, - }, - 'toString', - { - enumerable: false, - configurable: true, - value: () => - `module{moduleId=${feature.moduleId},pluginId=${feature.pluginId}}`, - }, - ); - } - // Ignore unknown feature types. - return undefined; - }) - .filter(Boolean) as BackendFeatureMeta[]; - } - return []; - }) - .flat(); + const installedPlugins: Map = + new Map(); + const registrations = rawRegistrations + .filter(registration => registration.featureType === 'registrations') + .flatMap(registration => registration.getRegistrations()); + const plugins = registrations.filter( + registration => registration.type === 'plugin', + ); + const modules = registrations.filter( + registration => registration.type === 'module', + ); + for (const plugin of plugins) { + const { pluginId } = plugin; + if (!installedPlugins.get(pluginId)) { + installedPlugins.set(pluginId, { + pluginId, + modules: [], + }); + } + } + for (const module of modules) { + const { pluginId, moduleId } = module; + const installedPlugin = installedPlugins.get(pluginId); + if (installedPlugin) { + (installedPlugin.modules as Array<{ moduleId: string }>).push({ + moduleId, + }); + } + } + return createServiceFactory({ - service: instanceMetadataServiceRef, + service: coreServices.rootInstanceMetadata, deps: {}, - factory: async () => ({ getInstalledFeatures: () => installedFeatures }), + factory: async () => { + const readonlyInstalledPlugins = deepFreeze([ + ...installedPlugins.values(), + ]); + const instanceMetadata = { + getInstalledPlugins: () => Promise.resolve(readonlyInstalledPlugins), + }; + + return instanceMetadata; + }, }); } export class BackendInitializer { - #startPromise?: Promise; + #startPromise?: Promise<{ result: BackendStartupResult }>; #stopPromise?: Promise; #registrations = new Array(); #extensionPoints = new Map(); @@ -231,7 +226,7 @@ export class BackendInitializer { } } - async start(): Promise { + async start(): Promise<{ result: BackendStartupResult }> { if (this.#startPromise) { throw new Error('Backend has already started'); } @@ -242,10 +237,10 @@ export class BackendInitializer { instanceRegistry.register(this); this.#startPromise = this.#doStart(); - await this.#startPromise; + return await this.#startPromise; } - async #doStart(): Promise { + async #doStart(): Promise<{ result: BackendStartupResult }> { this.#serviceRegistry.checkForCircularDeps(); for (const feature of this.#registeredFeatures) { @@ -255,7 +250,7 @@ export class BackendInitializer { await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders); this.#serviceRegistry.add( - createInstanceMetadataServiceFactory(this.#registrations), + createRootInstanceMetadataServiceFactory(this.#registrations), ); // This makes sure that any uncaught errors or unhandled rejections are @@ -339,26 +334,26 @@ export class BackendInitializer { } } - const allPluginIds = [...pluginInits.keys()]; - - const initLogger = createInitializationLogger( - allPluginIds, - await this.#serviceRegistry.get(coreServices.rootLogger, 'root'), - ); + const pluginIds = [...pluginInits.keys()]; const rootConfig = await this.#serviceRegistry.get( coreServices.rootConfig, 'root', ); + const rootLogger = await this.#serviceRegistry.get( + coreServices.rootLogger, + 'root', + ); + + const resultCollector = createInitializationResultCollector({ + pluginIds, + logger: rootLogger, + allowBootFailurePredicate: createAllowBootFailurePredicate(rootConfig), + }); // All plugins are initialized in parallel - const results = await Promise.allSettled( - allPluginIds.map(async pluginId => { - const isBootFailurePermitted = this.#getPluginBootFailurePredicate( - pluginId, - rootConfig, - ); - + await Promise.all( + pluginIds.map(async pluginId => { try { // Initialize all eager services await this.#serviceRegistry.initializeEagerServicesWithScope( @@ -389,37 +384,21 @@ export class BackendInitializer { } await tree.parallelTopologicalTraversal( async ({ moduleId, moduleInit }) => { - const isModuleBootFailurePermitted = - this.#getPluginModuleBootFailurePredicate( - pluginId, - moduleId, - rootConfig, - ); - try { const moduleDeps = await this.#getInitDeps( moduleInit.init.deps, pluginId, moduleId, ); - await moduleInit.init.func(moduleDeps).catch(error => { - throw new ForwardedError( - `Module '${moduleId}' for plugin '${pluginId}' startup failed`, - error, - ); - }); + await moduleInit.init.func(moduleDeps); + resultCollector.onPluginModuleResult(pluginId, moduleId); } catch (error: unknown) { assertError(error); - if (isModuleBootFailurePermitted) { - initLogger.onPermittedPluginModuleFailure( - pluginId, - moduleId, - error, - ); - } else { - initLogger.onPluginModuleFailed(pluginId, moduleId, error); - throw error; - } + resultCollector.onPluginModuleResult( + pluginId, + moduleId, + error, + ); } }, ); @@ -433,46 +412,36 @@ export class BackendInitializer { pluginInit.init.deps, pluginId, ); - await pluginInit.init.func(pluginDeps).catch(error => { - throw new ForwardedError( - `Plugin '${pluginId}' startup failed`, - error, - ); - }); + await pluginInit.init.func(pluginDeps); } - initLogger.onPluginStarted(pluginId); + resultCollector.onPluginResult(pluginId); // Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully const lifecycleService = await this.#getPluginLifecycleImpl(pluginId); await lifecycleService.startup(); } catch (error: unknown) { assertError(error); - if (isBootFailurePermitted) { - initLogger.onPermittedPluginFailure(pluginId, error); - } else { - initLogger.onPluginFailed(pluginId, error); - throw error; - } + resultCollector.onPluginResult(pluginId, error); } }), - ); + ).catch(error => { + throw new ForwardedError( + 'Unexpected uncaught backend startup error', + error, + ); + }); - const initErrors = results.flatMap(r => - r.status === 'rejected' ? [r.reason] : [], - ); - if (initErrors.length === 1) { - throw initErrors[0]; - } else if (initErrors.length > 1) { - // TODO(Rugvip): Seems like there aren't proper types for AggregateError yet - throw new (AggregateError as any)(initErrors, 'Backend startup failed'); + const result = resultCollector.finalize(); + if (result.outcome === 'failure') { + throw new BackendStartupError(result); } // Once all plugins and modules have been initialized, we can signal that the backend has started up successfully const lifecycleService = await this.#getRootLifecycleImpl(); await lifecycleService.startup(); - initLogger.onAllStarted(); + return { result }; } // It's fine to call .stop() multiple times, which for example can happen with manual stop + process exit @@ -661,38 +630,6 @@ export class BackendInitializer { } } } - - #getPluginBootFailurePredicate(pluginId: string, config?: Config): boolean { - const defaultStartupBootFailureValue = - config?.getOptionalString( - 'backend.startup.default.onPluginBootFailure', - ) ?? 'abort'; - - const pluginStartupBootFailureValue = - config?.getOptionalString( - `backend.startup.plugins.${pluginId}.onPluginBootFailure`, - ) ?? defaultStartupBootFailureValue; - - return pluginStartupBootFailureValue === 'continue'; - } - - #getPluginModuleBootFailurePredicate( - pluginId: string, - moduleId: string, - config?: Config, - ): boolean { - const defaultStartupBootFailureValue = - config?.getOptionalString( - 'backend.startup.default.onPluginModuleBootFailure', - ) ?? 'abort'; - - const pluginModuleStartupBootFailureValue = - config?.getOptionalString( - `backend.startup.plugins.${pluginId}.modules.${moduleId}.onPluginModuleBootFailure`, - ) ?? defaultStartupBootFailureValue; - - return pluginModuleStartupBootFailureValue === 'continue'; - } } function toInternalBackendFeature( diff --git a/packages/backend-app-api/src/wiring/BackendStartupError.ts b/packages/backend-app-api/src/wiring/BackendStartupError.ts new file mode 100644 index 0000000000..7876b2c236 --- /dev/null +++ b/packages/backend-app-api/src/wiring/BackendStartupError.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CustomErrorBase } from '@backstage/errors'; +import { BackendStartupResult } from './types'; + +function formatMessage(startupResult: BackendStartupResult): string { + const parts: string[] = [ + 'Backend startup failed due to the following errors:', + ]; + const failures: string[] = []; + + for (const plugin of startupResult.plugins) { + if (plugin.failure && !plugin.failure.allowed) { + failures.push( + ` Plugin '${plugin.pluginId}' startup failed; caused by ${plugin.failure.error}`, + ); + } + + for (const mod of plugin.modules) { + if (mod.failure && !mod.failure.allowed) { + failures.push( + ` Module '${mod.moduleId}' for plugin '${plugin.pluginId}' startup failed; caused by ${mod.failure.error}`, + ); + } + } + } + + if (failures.length > 0) { + parts.push(...failures); + } + + return parts.join('\n'); +} + +/** + * Error thrown when backend startup fails. + * Includes detailed startup results for all plugins and modules. + * + * @public + */ +export class BackendStartupError extends CustomErrorBase { + name = 'BackendStartupError' as const; + + /** + * The startup results for all plugins and modules. + */ + readonly #startupResult: BackendStartupResult; + + constructor(startupResult: BackendStartupResult) { + super(formatMessage(startupResult)); + this.#startupResult = startupResult; + } + + get result(): BackendStartupResult { + return this.#startupResult; + } +} diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index 49a8bbbbfb..79ee55780f 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -17,7 +17,7 @@ import { BackendFeature, ServiceFactory } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; import { unwrapFeature } from './helpers'; -import { Backend } from './types'; +import { Backend, BackendStartupResult } from './types'; export class BackstageBackend implements Backend { #initializer: BackendInitializer; @@ -34,8 +34,8 @@ export class BackstageBackend implements Backend { } } - async start(): Promise { - await this.#initializer.start(); + async start(): Promise<{ result: BackendStartupResult }> { + return await this.#initializer.start(); } async stop(): Promise { diff --git a/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.test.ts b/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.test.ts new file mode 100644 index 0000000000..f12998aa1d --- /dev/null +++ b/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.test.ts @@ -0,0 +1,520 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { createAllowBootFailurePredicate } from './createAllowBootFailurePredicate'; + +describe('createAllowBootFailurePredicate', () => { + describe('when no config is provided', () => { + it('should default to abort for plugins', () => { + const predicate = createAllowBootFailurePredicate(); + expect(predicate('test-plugin')).toBe(false); + }); + + it('should default to abort for modules', () => { + const predicate = createAllowBootFailurePredicate(); + expect(predicate('test-plugin', 'test-module')).toBe(false); + }); + }); + + describe('default plugin boot failure configuration', () => { + it('should use abort as default when not configured', () => { + const config = mockServices.rootConfig(); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(false); + }); + + it('should use continue when default is set to continue', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'continue', + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + }); + + it('should use abort when default is set to abort', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'abort', + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(false); + }); + }); + + describe('default module boot failure configuration', () => { + it('should use abort as default when not configured', () => { + const config = mockServices.rootConfig(); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(false); + }); + + it('should use continue when default is set to continue', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'continue', + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(true); + }); + + it('should use abort when default is set to abort', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(false); + }); + }); + + describe('plugin-specific overrides', () => { + it('should override default with plugin-specific continue', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'abort', + }, + plugins: { + 'test-plugin': { + onPluginBootFailure: 'continue', + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + }); + + it('should override default with plugin-specific abort', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'continue', + }, + plugins: { + 'test-plugin': { + onPluginBootFailure: 'abort', + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(false); + }); + + it('should use default when plugin-specific config is not set', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'continue', + }, + plugins: { + 'other-plugin': { + onPluginBootFailure: 'abort', + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + }); + + it('should handle multiple plugins independently', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'abort', + }, + plugins: { + 'plugin-a': { + onPluginBootFailure: 'continue', + }, + 'plugin-b': { + onPluginBootFailure: 'abort', + }, + 'plugin-c': { + // No override, uses default + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('plugin-a')).toBe(true); + expect(predicate('plugin-b')).toBe(false); + expect(predicate('plugin-c')).toBe(false); + }); + }); + + describe('module-specific overrides', () => { + it('should override default with module-specific continue', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'abort', + }, + plugins: { + 'test-plugin': { + modules: { + 'test-module': { + onPluginModuleBootFailure: 'continue', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(true); + }); + + it('should override default with module-specific abort', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'continue', + }, + plugins: { + 'test-plugin': { + modules: { + 'test-module': { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(false); + }); + + it('should use default when module-specific config is not set', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'continue', + }, + plugins: { + 'test-plugin': { + modules: { + 'other-module': { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(true); + }); + + it('should handle multiple modules independently', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'abort', + }, + plugins: { + 'test-plugin': { + modules: { + 'module-a': { + onPluginModuleBootFailure: 'continue', + }, + 'module-b': { + onPluginModuleBootFailure: 'abort', + }, + 'module-c': { + // No override, uses default + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'module-a')).toBe(true); + expect(predicate('test-plugin', 'module-b')).toBe(false); + expect(predicate('test-plugin', 'module-c')).toBe(false); + }); + + it('should handle modules across different plugins', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'abort', + }, + plugins: { + 'plugin-a': { + modules: { + 'module-x': { + onPluginModuleBootFailure: 'continue', + }, + }, + }, + 'plugin-b': { + modules: { + 'module-y': { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('plugin-a', 'module-x')).toBe(true); + expect(predicate('plugin-b', 'module-y')).toBe(false); + }); + }); + + describe('combined plugin and module configurations', () => { + it('should use module config when both plugin and module configs exist', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'abort', + onPluginModuleBootFailure: 'abort', + }, + plugins: { + 'test-plugin': { + onPluginBootFailure: 'continue', + modules: { + 'test-module': { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + expect(predicate('test-plugin', 'test-module')).toBe(false); + }); + + it('should use plugin default when module config does not exist', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'abort', + onPluginModuleBootFailure: 'abort', + }, + plugins: { + 'test-plugin': { + onPluginBootFailure: 'continue', + modules: { + 'other-module': { + onPluginModuleBootFailure: 'abort', + }, + }, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + expect(predicate('test-plugin', 'test-module')).toBe(false); // Uses default module config + }); + }); + + describe('edge cases', () => { + it('should handle empty plugins config', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginBootFailure: 'continue', + }, + plugins: {}, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin')).toBe(true); + }); + + it('should handle plugin with empty modules config', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'continue', + }, + plugins: { + 'test-plugin': { + modules: {}, + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(true); + }); + + it('should handle plugin without modules config', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + default: { + onPluginModuleBootFailure: 'continue', + }, + plugins: { + 'test-plugin': { + onPluginBootFailure: 'abort', + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('test-plugin', 'test-module')).toBe(true); + }); + + it('should handle case sensitivity correctly', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + startup: { + plugins: { + 'Test-Plugin': { + onPluginBootFailure: 'continue', + }, + }, + }, + }, + }, + }); + const predicate = createAllowBootFailurePredicate(config); + expect(predicate('Test-Plugin')).toBe(true); + expect(predicate('test-plugin')).toBe(false); // Different case + }); + }); + + describe('performance - configuration read upfront', () => { + it('should read configuration only once', () => { + const configData = { + backend: { + startup: { + default: { + onPluginBootFailure: 'continue', + }, + plugins: { + 'plugin-a': { + onPluginBootFailure: 'abort', + modules: { + 'module-x': { + onPluginModuleBootFailure: 'continue', + }, + }, + }, + }, + }, + }, + }; + const config = mockServices.rootConfig({ data: configData }); + const predicate = createAllowBootFailurePredicate(config); + + // Call predicate multiple times - should use cached values + expect(predicate('plugin-a')).toBe(false); + expect(predicate('plugin-a')).toBe(false); + expect(predicate('plugin-a', 'module-x')).toBe(true); + expect(predicate('plugin-a', 'module-x')).toBe(true); + expect(predicate('plugin-b')).toBe(true); // Uses default + }); + }); +}); diff --git a/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.ts b/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.ts new file mode 100644 index 0000000000..15b73cb8ed --- /dev/null +++ b/packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; + +export type AllowBootFailurePredicate = ( + pluginId: string, + moduleId?: string, +) => boolean; + +/** + * Creates a predicate function that determines whether a boot failure should be + * allowed for a given plugin or module based on configuration. + * + * @param config - The configuration object to read boot failure settings from + * @returns A predicate function that accepts a pluginId and optional moduleId, + * and returns true if boot failures should be allowed, false otherwise. + */ +export function createAllowBootFailurePredicate( + config?: Config, +): AllowBootFailurePredicate { + // Read default values upfront + const defaultPluginBootFailure = + config?.getOptionalString('backend.startup.default.onPluginBootFailure') ?? + 'abort'; + const defaultModuleBootFailure = + config?.getOptionalString( + 'backend.startup.default.onPluginModuleBootFailure', + ) ?? 'abort'; + + // Read plugin-specific overrides upfront + const pluginOverrides = new Map(); + const moduleOverrides = new Map>(); + + const pluginsConfig = config?.getOptionalConfig('backend.startup.plugins'); + if (pluginsConfig) { + for (const pluginId of pluginsConfig.keys()) { + const pluginConfig = pluginsConfig.getConfig(pluginId); + const pluginBootFailure = pluginConfig.getOptionalString( + 'onPluginBootFailure', + ); + if (pluginBootFailure) { + pluginOverrides.set(pluginId, pluginBootFailure); + } + + // Read module-specific overrides + const modulesConfig = pluginConfig.getOptionalConfig('modules'); + if (modulesConfig) { + const moduleMap = new Map(); + for (const moduleId of modulesConfig.keys()) { + const moduleConfig = modulesConfig.getConfig(moduleId); + const moduleBootFailure = moduleConfig.getOptionalString( + 'onPluginModuleBootFailure', + ); + if (moduleBootFailure) { + moduleMap.set(moduleId, moduleBootFailure); + } + } + if (moduleMap.size > 0) { + moduleOverrides.set(pluginId, moduleMap); + } + } + } + } + + return (pluginId: string, moduleId?: string): boolean => { + if (moduleId !== undefined) { + // Module-specific boot failure predicate + const moduleMap = moduleOverrides.get(pluginId); + const moduleBootFailure = + moduleMap?.get(moduleId) ?? defaultModuleBootFailure; + return moduleBootFailure === 'continue'; + } + // Plugin-specific boot failure predicate + const pluginBootFailure = + pluginOverrides.get(pluginId) ?? defaultPluginBootFailure; + return pluginBootFailure === 'continue'; + }; +} diff --git a/packages/backend-app-api/src/wiring/createInitializationLogger.ts b/packages/backend-app-api/src/wiring/createInitializationLogger.ts deleted file mode 100644 index 600826a435..0000000000 --- a/packages/backend-app-api/src/wiring/createInitializationLogger.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { RootLoggerService } from '@backstage/backend-plugin-api'; - -const LOGGER_INTERVAL_MAX = 60_000; - -function joinIds(ids: Iterable): string { - return [...ids].map(id => `'${id}'`).join(', '); -} - -export function createInitializationLogger( - pluginIds: string[], - rootLogger?: RootLoggerService, -): { - onPluginStarted(pluginId: string): void; - onPluginFailed(pluginId: string, error: Error): void; - onPermittedPluginFailure(pluginId: string, error: Error): void; - onPluginModuleFailed(pluginId: string, moduleId: string, error: Error): void; - onPermittedPluginModuleFailure( - pluginId: string, - moduleId: string, - error: Error, - ): void; - onAllStarted(): void; -} { - const logger = rootLogger?.child({ type: 'initialization' }); - const starting = new Set(pluginIds); - const started = new Set(); - - logger?.info(`Plugin initialization started: ${joinIds(pluginIds)}`); - - const getInitStatus = () => { - let status = ''; - if (started.size > 0) { - status = `, newly initialized: ${joinIds(started)}`; - started.clear(); - } - if (starting.size > 0) { - status += `, still initializing: ${joinIds(starting)}`; - } - return status; - }; - - // Periodically log the initialization status with a fibonacci backoff - let interval = 1000; - let prevInterval = 0; - let timeout: NodeJS.Timeout | undefined; - const onTimeout = () => { - logger?.info(`Plugin initialization in progress${getInitStatus()}`); - - const nextInterval = Math.min(interval + prevInterval, LOGGER_INTERVAL_MAX); - prevInterval = interval; - interval = nextInterval; - - timeout = setTimeout(onTimeout, nextInterval); - }; - timeout = setTimeout(onTimeout, interval); - - return { - onPluginStarted(pluginId: string) { - starting.delete(pluginId); - started.add(pluginId); - }, - onPluginFailed(pluginId: string, error: Error) { - starting.delete(pluginId); - const status = - starting.size > 0 - ? `, waiting for ${starting.size} other plugins to finish before shutting down the process` - : ''; - logger?.error( - `Plugin '${pluginId}' threw an error during startup${status}.`, - error, - ); - }, - onPermittedPluginFailure(pluginId: string, error: Error) { - starting.delete(pluginId); - logger?.error( - `Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin so startup will continue.`, - error, - ); - }, - onPluginModuleFailed(pluginId: string, moduleId: string, error: Error) { - const status = - starting.size > 0 - ? `, waiting for ${starting.size} other plugins to finish before shutting down the process` - : ''; - logger?.error( - `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup${status}.`, - error, - ); - }, - onPermittedPluginModuleFailure( - pluginId: string, - moduleId: string, - error: Error, - ) { - logger?.error( - `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin module so startup will continue.`, - error, - ); - }, - onAllStarted() { - logger?.info(`Plugin initialization complete${getInitStatus()}`); - - if (timeout) { - clearTimeout(timeout); - timeout = undefined; - } - }, - }; -} diff --git a/packages/backend-app-api/src/wiring/createInitializationResultCollector.ts b/packages/backend-app-api/src/wiring/createInitializationResultCollector.ts new file mode 100644 index 0000000000..b9ee4c227d --- /dev/null +++ b/packages/backend-app-api/src/wiring/createInitializationResultCollector.ts @@ -0,0 +1,178 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RootLoggerService } from '@backstage/backend-plugin-api'; +import { AllowBootFailurePredicate } from './createAllowBootFailurePredicate'; +import { + BackendStartupResult, + PluginStartupResult, + ModuleStartupResult, +} from './types'; + +const LOGGER_INTERVAL_MAX = 60_000; + +function joinIds(ids: Iterable): string { + return [...ids].map(id => `'${id}'`).join(', '); +} + +export function createInitializationResultCollector(options: { + pluginIds: string[]; + logger?: RootLoggerService; + allowBootFailurePredicate: AllowBootFailurePredicate; +}): { + onPluginResult(pluginId: string, error?: Error): void; + onPluginModuleResult(pluginId: string, moduleId: string, error?: Error): void; + finalize(): BackendStartupResult; +} { + const logger = options.logger?.child({ type: 'initialization' }); + const beginAt = new Date(); + const starting = new Set(options.pluginIds.toSorted()); + const started = new Set(); + + let hasDisallowedFailures = false; + + const pluginResults: PluginStartupResult[] = []; + const moduleResultsByPlugin: Map = new Map( + Array.from(starting).map(pluginId => [pluginId, []]), + ); + + logger?.info(`Plugin initialization started: ${joinIds(starting)}`); + + const getInitStatus = () => { + let status = ''; + if (started.size > 0) { + status = `, newly initialized: ${joinIds(started)}`; + started.clear(); + } + if (starting.size > 0) { + status += `, still initializing: ${joinIds(starting)}`; + } + return status; + }; + + // Periodically log the initialization status with a fibonacci backoff + let interval = 1000; + let prevInterval = 0; + let timeout: NodeJS.Timeout | undefined; + const onTimeout = () => { + logger?.info(`Plugin initialization in progress${getInitStatus()}`); + + const nextInterval = Math.min(interval + prevInterval, LOGGER_INTERVAL_MAX); + prevInterval = interval; + interval = nextInterval; + + timeout = setTimeout(onTimeout, nextInterval); + }; + timeout = setTimeout(onTimeout, interval); + + return { + onPluginResult(pluginId: string, error?: Error) { + starting.delete(pluginId); + started.add(pluginId); + + const modules = moduleResultsByPlugin.get(pluginId); + if (!modules) { + throw new Error( + `Failed to push plugin result for nonexistent plugin '${pluginId}'`, + ); + } + + if (!error) { + pluginResults.push({ + pluginId, + resultAt: new Date(), + modules, + }); + } else { + const allowed = options.allowBootFailurePredicate(pluginId); + pluginResults.push({ + pluginId, + resultAt: new Date(), + modules, + failure: { + error, + allowed, + }, + }); + if (allowed) { + logger?.error( + `Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin so startup will continue.`, + error, + ); + } else { + hasDisallowedFailures = true; + const status = + starting.size > 0 + ? `, waiting for ${starting.size} other plugins to finish before shutting down the process` + : ''; + logger?.error( + `Plugin '${pluginId}' threw an error during startup${status}.`, + error, + ); + } + } + }, + onPluginModuleResult(pluginId: string, moduleId: string, error?: Error) { + const moduleResults = moduleResultsByPlugin.get(pluginId); + if (!moduleResults) { + throw new Error( + `Failed to push module result for nonexistent plugin '${pluginId}'`, + ); + } + + if (!error) { + moduleResults.push({ moduleId, resultAt: new Date() }); + } else { + const allowed = options.allowBootFailurePredicate(pluginId, moduleId); + moduleResults.push({ + moduleId, + resultAt: new Date(), + failure: { error, allowed }, + }); + if (allowed) { + logger?.error( + `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin module so startup will continue.`, + error, + ); + } else { + hasDisallowedFailures = true; + const status = + starting.size > 0 + ? `, waiting for ${starting.size} other plugins to finish before shutting down the process` + : ''; + logger?.error( + `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup${status}.`, + error, + ); + } + } + }, + finalize() { + logger?.info(`Plugin initialization complete${getInitStatus()}`); + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + return { + beginAt, + resultAt: new Date(), + outcome: hasDisallowedFailures ? 'failure' : 'success', + plugins: pluginResults, + }; + }, + }; +} diff --git a/packages/backend-app-api/src/wiring/helpers.ts b/packages/backend-app-api/src/wiring/helpers.ts index ffb7e3b079..1be7f62da2 100644 --- a/packages/backend-app-api/src/wiring/helpers.ts +++ b/packages/backend-app-api/src/wiring/helpers.ts @@ -34,3 +34,22 @@ export function unwrapFeature( return feature; } + +/** @internal */ +export type DeepReadonly = { + readonly [K in keyof T]: T[K] extends object ? DeepReadonly : T[K]; +}; + +/** + * Deeply freezes an object by recursively freezing all of its properties. + * From https://gist.github.com/tkrotoff/e997cd6ff8d6cf6e51e6bb6146407fc3 + + * https://stackoverflow.com/a/69656011 + */ +export function deepFreeze(obj: T) { + // Can cause: "Type instantiation is excessively deep and possibly infinite." + // @ts-expect-error + Object.values(obj).forEach( + value => Object.isFrozen(value) || deepFreeze(value), + ); + return Object.freeze(obj) as DeepReadonly; +} diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts index 00e4a9d4b4..cc084e5f34 100644 --- a/packages/backend-app-api/src/wiring/index.ts +++ b/packages/backend-app-api/src/wiring/index.ts @@ -14,5 +14,12 @@ * limitations under the License. */ -export type { Backend, CreateSpecializedBackendOptions } from './types'; +export type { + Backend, + CreateSpecializedBackendOptions, + BackendStartupResult, + PluginStartupResult, + ModuleStartupResult, +} from './types'; export { createSpecializedBackend } from './createSpecializedBackend'; +export { BackendStartupError } from './BackendStartupError'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index bd761ff3c8..5782021656 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -26,7 +26,7 @@ import { */ export interface Backend { add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void; - start(): Promise; + start(): Promise<{ result: BackendStartupResult }>; stop(): Promise; } @@ -43,3 +43,88 @@ export interface CreateSpecializedBackendOptions { export type ServiceOrExtensionPoint = | ExtensionPoint | ServiceRef; + +/** + * Result of a module startup attempt. + * @public + */ +export interface ModuleStartupResult { + /** + * The time the module startup was completed. + */ + resultAt: Date; + + /** + * The ID of the module. + */ + moduleId: string; + + /** + * If the startup failed, this contains information about the failure. + */ + failure?: { + /** + * The error that occurred during startup, if any. + */ + error: Error; + /** + * Whether the failure was allowed. + */ + allowed: boolean; + }; +} + +/** + * Result of a plugin startup attempt. + * @public + */ +export interface PluginStartupResult { + /** + * The time the plugin startup was completed. + */ + resultAt: Date; + /** + * If the startup failed, this contains information about the failure. + */ + failure?: { + /** + * The error that occurred during startup, if any. + */ + error: Error; + /** + * Whether the failure was allowed. + */ + allowed: boolean; + }; + /** + * The ID of the plugin. + */ + pluginId: string; + /** + * Results for all modules belonging to this plugin. + */ + modules: ModuleStartupResult[]; +} + +/** + * Result of a backend startup attempt. + * @public + */ +export interface BackendStartupResult { + /** + * The time the backend startup started. + */ + beginAt: Date; + /** + * The time the backend startup was completed. + */ + resultAt: Date; + /** + * Results for all plugins that were attempted to start. + */ + plugins: PluginStartupResult[]; + /** + * The outcome of the backend startup. + */ + outcome: 'success' | 'failure'; +} diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 62c478f6f7..163264fe5b 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/backend-defaults +## 0.14.0-next.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies + +### 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-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/integration@1.18.3-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.13.1 + +### Patch Changes + +- 9bcfa77: Adjusted the log line wording of task worker starting +- 91ab2eb: Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- b2f6a5a: Fix #31348 issue where BitbucketUrlReader ignored provided token and instead always used integration credentials +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-permission-node@0.10.6 + +## 0.13.1-next.1 + +### Patch Changes + +- 91ab2eb: Fix a bug in the Gitlab URL reader where `search` did not handle multiple globs +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-app-api@1.3.0-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + ## 0.13.1-next.0 ### Patch Changes diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index f0632a37bb..6131169d6b 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -993,6 +993,13 @@ export interface Config { */ csp?: { [policyId: string]: string[] | false }; + /** + * Referrer Policy options + */ + referrer?: { + policy: string[]; + }; + /** * Options for the health check service and endpoint. */ diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 4884c9d30d..63ea7b4b55 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.13.1-next.0", + "version": "0.14.0-next.0", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -156,13 +156,12 @@ "@types/express": "^4.17.6", "archiver": "^7.0.0", "base64-stream": "^1.0.0", - "better-sqlite3": "^12.0.0", "compression": "^1.7.4", "concat-stream": "^2.0.0", "cookie": "^0.7.0", "cors": "^2.8.5", "cron": "^3.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "express-rate-limit": "^7.5.0", "fs-extra": "^11.2.0", @@ -179,7 +178,7 @@ "minimatch": "^9.0.0", "mysql2": "^3.0.0", "node-fetch": "^2.7.0", - "node-forge": "^1.3.1", + "node-forge": "^1.3.2", "p-limit": "^3.1.0", "path-to-regexp": "^8.0.0", "pg": "^8.11.3", @@ -204,7 +203,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@google-cloud/cloud-sql-connector": "^1.4.0", - "@types/archiver": "^6.0.0", + "@types/archiver": "^7.0.0", "@types/base64-stream": "^1.0.2", "@types/compression": "^1.7.5", "@types/concat-stream": "^2.0.0", @@ -214,6 +213,8 @@ "@types/pg-format": "^1.0.5", "@types/yauzl": "^2.10.0", "aws-sdk-client-mock": "^4.0.0", + "better-sqlite3": "^12.0.0", + "get-port": "^5.1.1", "http-errors": "^2.0.0", "msw": "^1.0.0", "node-mocks-http": "^1.0.0", @@ -221,11 +222,15 @@ "wait-for-expect": "^3.0.2" }, "peerDependencies": { - "@google-cloud/cloud-sql-connector": "^1.4.0" + "@google-cloud/cloud-sql-connector": "^1.4.0", + "better-sqlite3": "^12.0.0" }, "peerDependenciesMeta": { "@google-cloud/cloud-sql-connector": { "optional": true + }, + "better-sqlite3": { + "optional": true } }, "configSchema": "config.d.ts" diff --git a/packages/backend-defaults/report-alpha.api.md b/packages/backend-defaults/report-alpha.api.md index 58277b5e1e..c40517863a 100644 --- a/packages/backend-defaults/report-alpha.api.md +++ b/packages/backend-defaults/report-alpha.api.md @@ -5,6 +5,7 @@ ```ts import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { RootSystemMetadataService } from '@backstage/backend-plugin-api/alpha'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -21,5 +22,12 @@ export const actionsServiceFactory: ServiceFactory< 'singleton' >; +// @alpha +export const rootSystemMetadataServiceFactory: ServiceFactory< + RootSystemMetadataService, + 'root', + 'singleton' +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 4b201fded3..9f8116a996 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -39,6 +39,7 @@ import { actionsRegistryServiceFactory, actionsServiceFactory, } from '@backstage/backend-defaults/alpha'; +import { instanceMetadataServiceFactory } from './alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory'; export const defaultServiceFactories = [ auditorServiceFactory, @@ -65,6 +66,9 @@ export const defaultServiceFactories = [ // alpha services actionsRegistryServiceFactory, actionsServiceFactory, + + // Unexported alpha services kept around for compatibility reasons + instanceMetadataServiceFactory, ]; /** diff --git a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.test.ts new file mode 100644 index 0000000000..73de60efdd --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.test.ts @@ -0,0 +1,60 @@ +/* + * 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, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { + instanceMetadataServiceFactory, + instanceMetadataServiceRef, +} from './instanceMetadataServiceFactory'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import Router from 'express-promise-router'; +import request from 'supertest'; + +describe('instanceMetadataServiceFactory', () => { + it('should create an instance metadata service', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + instanceMetadata: instanceMetadataServiceRef, + httpRouter: coreServices.httpRouter, + }, + async init({ instanceMetadata, httpRouter }) { + const router = Router(); + router.get('/instance-metadata', async (_, res) => { + const metadata = await instanceMetadata.getInstalledFeatures(); + res.json(metadata); + }); + httpRouter.use(router); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, instanceMetadataServiceFactory], + }); + + const response = await request(server) + .get('/api/my-plugin/instance-metadata') + .expect(200); + expect(response.body).toEqual([{ pluginId: 'my-plugin', type: 'plugin' }]); + }); +}); diff --git a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts new file mode 100644 index 0000000000..eb50037511 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts @@ -0,0 +1,88 @@ +/* + * 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, + createServiceFactory, + createServiceRef, +} from '@backstage/backend-plugin-api'; + +/* + * NOTE(freben): We have moved over to coreServices.rootInstanceMetadata. This + * old alpha implementation is kept around for a little longer for backward + * compatibility reasons. + */ + +/** + * @internal + */ +export type BackendFeatureMeta = + | { + type: 'plugin'; + pluginId: string; + } + | { + type: 'module'; + pluginId: string; + moduleId: string; + }; + +/** + * @internal + */ +export interface InstanceMetadataService { + getInstalledFeatures: () => BackendFeatureMeta[]; +} + +/** + * @internal + */ +export const instanceMetadataServiceRef = + createServiceRef({ + id: 'core.instanceMetadata', + }); + +/** + * @internal + */ +export const instanceMetadataServiceFactory = createServiceFactory({ + service: instanceMetadataServiceRef, + deps: { + instanceMetadata: coreServices.rootInstanceMetadata, + }, + factory: async ({ instanceMetadata }) => { + const plugins = await instanceMetadata.getInstalledPlugins(); + const features: BackendFeatureMeta[] = []; + for (const plugin of plugins) { + features.push({ + type: 'plugin' as const, + pluginId: plugin.pluginId, + }); + for (const module of plugin.modules) { + features.push({ + type: 'module' as const, + pluginId: plugin.pluginId, + moduleId: module.moduleId, + }); + } + } + const service: InstanceMetadataService = { + getInstalledFeatures: () => features, + }; + + return service; + }, +}); diff --git a/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/RootSystemMetadataService.test.ts b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/RootSystemMetadataService.test.ts new file mode 100644 index 0000000000..13da5ae3c0 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/RootSystemMetadataService.test.ts @@ -0,0 +1,144 @@ +/* + * 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { + coreServices, + createBackendPlugin, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import Router from 'express-promise-router'; +import request from 'supertest'; +import { rootSystemMetadataServiceFactory } from './rootSystemMetadataServiceFactory'; +import { rootSystemMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; + +describe('SystemMetadataService', () => { + describe('returns plugins from config', () => { + const testPlugin = createBackendPlugin({ + pluginId: 'test-plugin', + register(reg) { + reg.registerInit({ + deps: { + systemMetadata: rootSystemMetadataServiceRef, + rootHttpRouter: coreServices.rootHttpRouter, + }, + init: async ({ systemMetadata, rootHttpRouter }) => { + const router = Router(); + router.use('/plugins', async (_, res) => { + res.json(await systemMetadata.getInstalledPlugins()); + }); + rootHttpRouter.use('/systemMetadata', router); + }, + }); + }, + }); + + it('should list all known instances', async () => { + const { server } = await startTestBackend({ + features: [ + testPlugin, + rootSystemMetadataServiceFactory, + mockServices.rootConfig.factory({ + data: { + backend: { + listen: { + port: 0, + }, + baseUrl: `http://localhost:0`, + }, + }, + }), + ], + }); + + const instanceResponse = await request(server).get( + `/systemMetadata/plugins`, + ); + + expect(instanceResponse.status).toBe(200); + expect(instanceResponse.body).toMatchObject([ + { + pluginId: 'test-plugin', + }, + ]); + }); + + it('should react to config updates', async () => { + const config = mockServices.rootConfig({ + data: { + backend: { + listen: { + port: 0, + }, + baseUrl: `http://localhost:0`, + }, + }, + }); + const configFactory = createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }); + const { server } = await startTestBackend({ + features: [testPlugin, configFactory, rootSystemMetadataServiceFactory], + }); + + const initialResponse = await request(server).get( + `/systemMetadata/plugins`, + ); + + expect(initialResponse.status).toBe(200); + expect(initialResponse.body).toMatchObject([ + { + pluginId: 'test-plugin', + }, + ]); + + config.update({ + data: { + backend: { + listen: { + port: 0, + }, + baseUrl: `http://localhost:0`, + }, + discovery: { + endpoints: [ + { + target: `http://test.internal`, + plugins: ['your-new-plugin'], + }, + ], + }, + }, + }); + + const responseAfterUpdate = await request(server).get( + `/systemMetadata/plugins`, + ); + + expect(responseAfterUpdate.status).toBe(200); + expect(responseAfterUpdate.body).toMatchObject([ + { + pluginId: 'your-new-plugin', + }, + { + pluginId: 'test-plugin', + }, + ]); + }); + }); +}); diff --git a/packages/core-plugin-api/src/apis/definitions/alpha.ts b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts similarity index 69% rename from packages/core-plugin-api/src/apis/definitions/alpha.ts rename to packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts index e7f6f13fef..cff4eccdbe 100644 --- a/packages/core-plugin-api/src/apis/definitions/alpha.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - translationApiRef, - type TranslationApi, - type TranslationFunction, - type TranslationSnapshot, -} from './TranslationApi'; -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; +export { rootSystemMetadataServiceFactory } from './rootSystemMetadataServiceFactory'; +export { DefaultRootSystemMetadataService } from './lib/DefaultRootSystemMetadataService'; diff --git a/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/lib/DefaultRootSystemMetadataService.ts b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/lib/DefaultRootSystemMetadataService.ts new file mode 100644 index 0000000000..edbffdfb0e --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/lib/DefaultRootSystemMetadataService.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + LoggerService, + RootConfigService, + RootInstanceMetadataService, +} from '@backstage/backend-plugin-api'; +import { + RootSystemMetadataService, + RootSystemMetadataServicePluginInfo, +} from '@backstage/backend-plugin-api/alpha'; +import { getEndpoints } from '../../../../entrypoints/discovery/parsing'; +import { Config } from '@backstage/config'; + +function getPlugins(config: Config): string[] { + const endpoints = getEndpoints(config); + return Array.from(new Set(endpoints.flatMap(endpoint => endpoint.plugins))); +} + +/** + * @alpha + */ +export class DefaultRootSystemMetadataService + implements RootSystemMetadataService +{ + #plugins: string[]; + #instanceMetadata: RootInstanceMetadataService; + constructor(options: { + logger: LoggerService; + config: RootConfigService; + instanceMetadata: RootInstanceMetadataService; + }) { + const { config } = options; + this.#plugins = getPlugins(config); + config.subscribe?.(() => { + this.#plugins = getPlugins(config); + }); + this.#instanceMetadata = options.instanceMetadata; + } + + public static create(pluginEnv: { + logger: LoggerService; + config: RootConfigService; + instanceMetadata: RootInstanceMetadataService; + }) { + return new DefaultRootSystemMetadataService(pluginEnv); + } + + public async getInstalledPlugins(): Promise< + RootSystemMetadataServicePluginInfo[] + > { + const plugins = this.#plugins.map(pluginId => ({ pluginId })); + + for (const plugin of await this.#instanceMetadata.getInstalledPlugins()) { + plugins.push({ pluginId: plugin.pluginId }); + } + + return plugins; + } +} diff --git a/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.ts new file mode 100644 index 0000000000..5ee93d03d5 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { DefaultRootSystemMetadataService } from './lib/DefaultRootSystemMetadataService'; +import { rootSystemMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; + +/** + * Metadata about an entire Backstage system, a collection of Backstage instances. + * + * @alpha + */ +export const rootSystemMetadataServiceFactory = createServiceFactory({ + service: rootSystemMetadataServiceRef, + deps: { + logger: coreServices.rootLogger, + config: coreServices.rootConfig, + instanceMetadata: coreServices.rootInstanceMetadata, + }, + async factory({ logger, config, instanceMetadata }) { + return DefaultRootSystemMetadataService.create({ + logger, + config, + instanceMetadata, + }); + }, +}); diff --git a/packages/backend-defaults/src/alpha/index.ts b/packages/backend-defaults/src/alpha/index.ts index 13bb439acd..c17e0e71cb 100644 --- a/packages/backend-defaults/src/alpha/index.ts +++ b/packages/backend-defaults/src/alpha/index.ts @@ -13,5 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; export { actionsServiceFactory } from './entrypoints/actions'; +export { rootSystemMetadataServiceFactory } from './entrypoints/rootSystemMetadata'; diff --git a/packages/backend-defaults/src/entrypoints/auth/plugin/keys/DatabasePluginKeySource.ts b/packages/backend-defaults/src/entrypoints/auth/plugin/keys/DatabasePluginKeySource.ts index e6a7bbcfc6..562086a07f 100644 --- a/packages/backend-defaults/src/entrypoints/auth/plugin/keys/DatabasePluginKeySource.ts +++ b/packages/backend-defaults/src/entrypoints/auth/plugin/keys/DatabasePluginKeySource.ts @@ -102,7 +102,7 @@ export class DatabasePluginKeySource implements PluginKeySource { await this.keyStore.addKey({ id: kid, - key: publicKey as InternalKey, + key: publicKey as unknown as InternalKey, expiresAt: new Date( Date.now() + this.keyDurationSeconds * diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts index 5a4631283e..18ed72a085 100644 --- a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts @@ -23,6 +23,7 @@ import { import { readHttpServerOptions } from '../rootHttpRouter/http/config'; import { SrvResolvers } from './SrvResolvers'; import { trimEnd } from 'lodash'; +import { getEndpoints } from './parsing'; type Resolver = (pluginId: string) => Promise; @@ -238,25 +239,7 @@ export class HostDiscovery implements DiscoveryService { const endpoints = defaultEndpoints?.slice() ?? []; // Allow config to override the default endpoints - const endpointConfigs = config.getOptionalConfigArray( - 'discovery.endpoints', - ); - for (const endpointConfig of endpointConfigs ?? []) { - if (typeof endpointConfig.get('target') === 'string') { - endpoints.push({ - target: endpointConfig.getString('target'), - plugins: endpointConfig.getStringArray('plugins'), - }); - } else { - endpoints.push({ - target: { - internal: endpointConfig.getOptionalString('target.internal'), - external: endpointConfig.getOptionalString('target.external'), - }, - plugins: endpointConfig.getStringArray('plugins'), - }); - } - } + endpoints.push(...getEndpoints(config)); // Build up a new set of resolvers const internalResolvers: Map = new Map(); diff --git a/packages/backend-defaults/src/entrypoints/discovery/parsing.ts b/packages/backend-defaults/src/entrypoints/discovery/parsing.ts new file mode 100644 index 0000000000..3a02fc4f51 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/discovery/parsing.ts @@ -0,0 +1,41 @@ +/* + * 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 type { HostDiscoveryEndpoint } from './HostDiscovery'; + +export function getEndpoints(config: Config): HostDiscoveryEndpoint[] { + const endpoints: HostDiscoveryEndpoint[] = []; + // Allow config to override the default endpoints + const endpointConfigs = config.getOptionalConfigArray('discovery.endpoints'); + for (const endpointConfig of endpointConfigs ?? []) { + if (typeof endpointConfig.get('target') === 'string') { + endpoints.push({ + target: endpointConfig.getString('target'), + plugins: endpointConfig.getStringArray('plugins'), + }); + } else { + endpoints.push({ + target: { + internal: endpointConfig.getOptionalString('target.internal'), + external: endpointConfig.getOptionalString('target.external'), + }, + plugins: endpointConfig.getStringArray('plugins'), + }); + } + } + return endpoints; +} diff --git a/packages/backend-defaults/src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.test.ts index 578c76378a..083207d6aa 100644 --- a/packages/backend-defaults/src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.test.ts @@ -49,9 +49,10 @@ describe('permissionsRegistryServiceFactory', () => { }), ], }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'"`, - ); + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Backend startup failed due to the following errors: + Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'" + `); await expect( startTestBackend({ @@ -75,8 +76,9 @@ describe('permissionsRegistryServiceFactory', () => { }), ], }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'"`, - ); + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Backend startup failed due to the following errors: + Plugin 'test' startup failed; caused by Error: Resource type 'some-resource' belongs to plugin 'other', but was used with plugin 'test'" + `); }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.test.ts index 97daef359a..38cdf75396 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.test.ts @@ -39,6 +39,9 @@ describe('readHelmetOptions', () => { crossOriginOpenerPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false, + referrerPolicy: { + policy: ['no-referrer'], + }, }); }); @@ -50,6 +53,9 @@ describe('readHelmetOptions', () => { scriptSrcAttr: ['custom'], 'object-src': ['asd'], }, + referrer: { + policy: ['foo', 'bar'], + }, }); expect(readHelmetOptions(config)).toEqual({ contentSecurityPolicy: { @@ -71,6 +77,9 @@ describe('readHelmetOptions', () => { crossOriginOpenerPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false, + referrerPolicy: { + policy: ['foo', 'bar'], + }, }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.ts index fc1fc59bcb..040da63e5f 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/readHelmetOptions.ts @@ -46,6 +46,7 @@ export function readHelmetOptions(config?: Config): HelmetOptions { crossOriginOpenerPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false, + referrerPolicy: readReferrerPolicy(config), }; } @@ -113,3 +114,30 @@ export function applyCspDirectives( return result; } + +type ReferrerPolicy = Record | undefined; + +/** + * Attempts to read the ReferrerPolicy from the backend configuration object. + * + * @example + * ```yaml + * backend: + * referrer: + * policy: ["strict-origin-when-cross-origin"] + * ``` + */ +function readReferrerPolicy(config?: Config): ReferrerPolicy { + const cc = config?.getOptionalConfig('referrer'); + const result: Record = {}; + + if (!cc) { + result.policy = ['no-referrer']; + } else { + for (const key of cc.keys()) { + result[key] = cc.getStringArray(key); + } + } + + return result; +} diff --git a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts index c48cef2f1f..0455912ca9 100644 --- a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts +++ b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts @@ -102,7 +102,9 @@ export class WinstonLogger implements RootLoggerService { return obj; } - obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***'); + if (typeof obj[MESSAGE] === 'string') { + obj[MESSAGE] = obj[MESSAGE].replace(redactionPattern, '***'); + } return obj; })(), @@ -157,8 +159,11 @@ export class WinstonLogger implements RootLoggerService { format.printf((info: TransformableInfo) => { const { timestamp, level, message, plugin, service, ...fields } = info; const prefix = plugin || service; - const timestampColor = colorizer.colorize('timestamp', timestamp); - const prefixColor = colorizer.colorize('prefix', prefix); + const timestampColor = colorizer.colorize( + 'timestamp', + String(timestamp), + ); + const prefixColor = colorizer.colorize('prefix', String(prefix)); const extraFields = Object.entries(fields) .map(([key, value]) => { diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts index de1b6e4caf..5bd3fb38cb 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts @@ -46,7 +46,7 @@ describe('TaskWorker', () => { const settings: TaskSettingsV2 = { version: 2, cadence: '*/2 * * * * *', - initialDelayDuration: Duration.fromObject({ seconds: 1 }).toISO()!, + initialDelayDuration: Duration.fromObject({ seconds: 3 }).toISO()!, // over 1 second, to cover for 1-second granularity in some database timestamps timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, }; @@ -65,7 +65,7 @@ describe('TaskWorker', () => { expect(JSON.parse(row.settings_json)).toEqual({ version: 2, cadence: '*/2 * * * * *', - initialDelayDuration: 'PT1S', + initialDelayDuration: 'PT3S', timeoutAfterDuration: 'PT1M', }); await expect(TaskWorker.taskStates(knex)).resolves.toEqual( @@ -80,6 +80,10 @@ describe('TaskWorker', () => { ]), ); + // Note that this is timing sensitive - if things take too long between + // persistTask and findReadyTask, this test will fail. We set some margin + // of initialDelayDuration to cover for this, but if this turns out to be + // flaky, we may have to revisit how this test is written. await expect(worker.findReadyTask()).resolves.toEqual({ result: 'not-ready-yet', }); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts index eb040ef415..e47967de90 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts @@ -280,8 +280,10 @@ export class TaskWorker { .sendAt() .minus({ seconds: 1 }) // immediately, if "* * * * * *" .toUTC(); + // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same + const timeConverted = DateTime.fromJSDate(time.toJSDate()); - nextStartAt = this.nextRunAtRaw(time); + nextStartAt = this.nextRunAtRaw(timeConverted); startAt ||= nextStartAt; } else if (isManual) { nextStartAt = this.knex.raw('null'); @@ -418,8 +420,10 @@ export class TaskWorker { if (isCron) { const time = new CronTime(settings.cadence).sendAt().toUTC(); this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); + // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same + const timeConverted = DateTime.fromJSDate(time.toJSDate()); - nextRun = this.nextRunAtRaw(time); + nextRun = this.nextRunAtRaw(timeConverted); } else if (isManual) { nextRun = this.knex.raw('null'); } else { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts index 3a228f1e9c..ff6ec3bb27 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts @@ -68,11 +68,11 @@ export class BitbucketCloudUrlReader implements UrlReaderService { ) { this.integration = integration; this.deps = deps; - const { host, username, appPassword } = integration.config; + const { host, username, appPassword, token } = integration.config; - if (username && !appPassword) { + if (username && !token && !appPassword) { throw new Error( - `Bitbucket Cloud integration for '${host}' has configured a username but is missing a required appPassword.`, + `Bitbucket Cloud integration for '${host}' has configured a username but is missing a required token or appPassword.`, ); } } @@ -228,8 +228,10 @@ export class BitbucketCloudUrlReader implements UrlReaderService { } toString() { - const { host, username, appPassword } = this.integration.config; - const authed = Boolean(username && appPassword); + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + const { host, username, appPassword, token } = this.integration.config; + const authed = Boolean(username && (token ?? appPassword)); return `bitbucketCloud{host=${host},authed=${authed}}`; } diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts index 65cd275574..c5ce323c54 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts @@ -670,6 +670,17 @@ describe('GitlabUrlReader', () => { ); }); + it('works when there are multiple globs', async () => { + const result = await gitlabProcessor.search( + 'https://gitlab.com/backstage/mock/tree/main/**/docs/**/index.*', + ); + expect(result.etag).toBe('sha123abc'); + expect(result.files.length).toBe(1); + expect(result.files[0].url).toBe( + 'https://gitlab.com/backstage/mock/tree/main/docs/index.md', + ); + }); + it('works for the naive case', async () => { const result = await gitlabProcessor.search( 'https://gitlab.com/backstage/mock/tree/main/**/index.*', diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts index 19f1846dda..ee290e31e6 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts @@ -319,14 +319,10 @@ export class GitlabUrlReader implements UrlReaderService { */ private getStaticPart(globPattern: string) { const segments = globPattern.split('/'); - let i = segments.length; - while ( - i > 0 && - new Minimatch(segments.slice(0, i).join('/')).match(globPattern) - ) { - i--; - } - return segments.slice(0, i).join('/'); + const globIndex = segments.findIndex(segment => segment.match(/[*?]/)); + return globIndex === -1 + ? globPattern + : segments.slice(0, globIndex).join('/'); } toString() { diff --git a/packages/backend-dev-utils/src/ipcClient.ts b/packages/backend-dev-utils/src/ipcClient.ts index 6e1d3d181e..814767347e 100644 --- a/packages/backend-dev-utils/src/ipcClient.ts +++ b/packages/backend-dev-utils/src/ipcClient.ts @@ -126,7 +126,7 @@ export class BackstageIpcClient { timeout.unref(); this.#handlers.set(id, responseHandler); - this.#sendMessage(request, (e: Error) => { + this.#sendMessage(request, undefined, undefined, (e: Error | null) => { if (e) { reject(e); } diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 30bce3a09d..25ae340cbf 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/backend-dynamic-feature-service +## 0.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/backend-openapi-utils@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.40-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## 0.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/plugin-scaffolder-node@0.12.1 + - @backstage/backend-openapi-utils@0.6.3 + - @backstage/plugin-app-node@0.1.39 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-search-common@1.2.21 + +## 0.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/backend-openapi-utils@0.6.3-next.1 + - @backstage/plugin-app-node@0.1.39-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-scaffolder-node@0.12.1-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + ## 0.7.6-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index d203e6d72a..b2f2a5902f 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.6-next.0", + "version": "0.7.7-next.0", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" @@ -70,7 +70,7 @@ "@manypkg/get-packages": "^1.1.3", "@module-federation/sdk": "^0.9.0", "chokidar": "^3.5.3", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "lodash": "^4.17.21", diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index f1917123a4..7f3a9328ae 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/backend-openapi-utils +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + +## 0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + ## 0.6.3-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index a70f729714..3c4908de61 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.3-next.0", + "version": "0.6.4-next.0", "description": "OpenAPI typescript support.", "backstage": { "role": "node-library" @@ -52,7 +52,7 @@ "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "ajv": "^8.16.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-openapi-validator": "^5.5.8", "express-promise-router": "^4.1.0", "get-port": "^5.1.1", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index fedc20cd49..617012d22b 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/backend-plugin-api +## 1.5.1-next.0 + +### 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-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.5.0 + +### Minor Changes + +- a17d9df: Promote `instanceMetadata` service to main entrypoint. +- 62fc2de: Explicitly mark `coreServices.rootInstanceMetadata` as a root service. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-node@0.10.6 + +## 1.5.0-next.2 + +### Minor Changes + +- 62fc2de: Explicitly mark `coreServices.rootInstanceMetadata` as a root service. + +## 1.5.0-next.1 + +### Minor Changes + +- a17d9df: Promote `instanceMetadata` service to main entrypoint. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + ## 1.4.5-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 78ff5d7131..be377d1d12 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.4.5-next.0", + "version": "1.5.1-next.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/report-alpha.api.md b/packages/backend-plugin-api/report-alpha.api.md index 9be7a29ed5..e06e0c45d2 100644 --- a/packages/backend-plugin-api/report-alpha.api.md +++ b/packages/backend-plugin-api/report-alpha.api.md @@ -103,28 +103,24 @@ export const actionsServiceRef: ServiceRef< 'singleton' >; -// @alpha (undocumented) -export type BackendFeatureMeta = - | { - type: 'plugin'; - pluginId: string; - } - | { - type: 'module'; - pluginId: string; - moduleId: string; - }; - -// @alpha (undocumented) -export interface InstanceMetadataService { +// @public (undocumented) +export interface RootSystemMetadataService { // (undocumented) - getInstalledFeatures: () => BackendFeatureMeta[]; + getInstalledPlugins: () => Promise< + ReadonlyArray + >; } -// @alpha (undocumented) -export const instanceMetadataServiceRef: ServiceRef< - InstanceMetadataService, - 'plugin', +// @public (undocumented) +export interface RootSystemMetadataServicePluginInfo { + // (undocumented) + readonly pluginId: string; +} + +// @alpha +export const rootSystemMetadataServiceRef: ServiceRef< + RootSystemMetadataService, + 'root', 'singleton' >; diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 52843dec96..c46ed254c8 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -232,6 +232,11 @@ export namespace coreServices { const rootLogger: ServiceRef; const scheduler: ServiceRef; const urlReader: ServiceRef; + const rootInstanceMetadata: ServiceRef< + RootInstanceMetadataService, + 'root', + 'singleton' + >; } // @public @@ -578,6 +583,24 @@ export interface RootHttpRouterService { use(path: string, handler: Handler): void; } +// @public (undocumented) +export interface RootInstanceMetadataService { + // (undocumented) + getInstalledPlugins: () => Promise< + ReadonlyArray + >; +} + +// @public (undocumented) +export interface RootInstanceMetadataServicePluginInfo { + // (undocumented) + readonly modules: ReadonlyArray<{ + moduleId: string; + }>; + // (undocumented) + readonly pluginId: string; +} + // @public export interface RootLifecycleService extends LifecycleService { // (undocumented) diff --git a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts b/packages/backend-plugin-api/src/alpha/RootSystemMetadataService.ts similarity index 67% rename from packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts rename to packages/backend-plugin-api/src/alpha/RootSystemMetadataService.ts index 869d361343..67cc43f8af 100644 --- a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts +++ b/packages/backend-plugin-api/src/alpha/RootSystemMetadataService.ts @@ -14,19 +14,14 @@ * limitations under the License. */ -/** @alpha */ -export type BackendFeatureMeta = - | { - type: 'plugin'; - pluginId: string; - } - | { - type: 'module'; - pluginId: string; - moduleId: string; - }; - -/** @alpha */ -export interface InstanceMetadataService { - getInstalledFeatures: () => BackendFeatureMeta[]; +/** @public */ +export interface RootSystemMetadataServicePluginInfo { + readonly pluginId: string; +} + +/** @public */ +export interface RootSystemMetadataService { + getInstalledPlugins: () => Promise< + ReadonlyArray + >; } diff --git a/packages/backend-plugin-api/src/alpha/index.ts b/packages/backend-plugin-api/src/alpha/index.ts index 5bb69eb4e2..56f02f5275 100644 --- a/packages/backend-plugin-api/src/alpha/index.ts +++ b/packages/backend-plugin-api/src/alpha/index.ts @@ -15,9 +15,9 @@ */ export type { - BackendFeatureMeta, - InstanceMetadataService, -} from './InstanceMetadataService'; + RootSystemMetadataServicePluginInfo, + RootSystemMetadataService, +} from './RootSystemMetadataService'; export type { ActionsRegistryService, @@ -30,5 +30,5 @@ export type { ActionsService, ActionsServiceAction } from './ActionsService'; export { actionsRegistryServiceRef, actionsServiceRef, - instanceMetadataServiceRef, + rootSystemMetadataServiceRef, } from './refs'; diff --git a/packages/backend-plugin-api/src/alpha/refs.ts b/packages/backend-plugin-api/src/alpha/refs.ts index 81996679f7..a890271364 100644 --- a/packages/backend-plugin-api/src/alpha/refs.ts +++ b/packages/backend-plugin-api/src/alpha/refs.ts @@ -16,15 +16,6 @@ import { createServiceRef } from '@backstage/backend-plugin-api'; -/** - * @alpha - */ -export const instanceMetadataServiceRef = createServiceRef< - import('./InstanceMetadataService').InstanceMetadataService ->({ - id: 'core.instanceMetadata', -}); - /** * Service for calling distributed actions * @@ -54,3 +45,14 @@ export const actionsRegistryServiceRef = createServiceRef< >({ id: 'alpha.core.actionsRegistry', }); + +/** + * Read information about your current Backstage deployment. + * @alpha + */ +export const rootSystemMetadataServiceRef = createServiceRef< + import('./RootSystemMetadataService').RootSystemMetadataService +>({ + id: 'alpha.core.rootSystemMetadata', + scope: 'root', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/RootInstanceMetadataService.ts b/packages/backend-plugin-api/src/services/definitions/RootInstanceMetadataService.ts new file mode 100644 index 0000000000..c324fed331 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/RootInstanceMetadataService.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @public */ +export interface RootInstanceMetadataServicePluginInfo { + readonly pluginId: string; + readonly modules: ReadonlyArray<{ + moduleId: string; + }>; +} + +/** @public */ +export interface RootInstanceMetadataService { + getInstalledPlugins: () => Promise< + ReadonlyArray + >; +} diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 8c8c6c0f82..887b05ee4e 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -277,4 +277,16 @@ export namespace coreServices { export const urlReader = createServiceRef< import('./UrlReaderService').UrlReaderService >({ id: 'core.urlReader' }); + + /** + * Information about the current Backstage instance. + * + * @public + */ + export const rootInstanceMetadata = createServiceRef< + import('./RootInstanceMetadataService').RootInstanceMetadataService + >({ + id: 'core.rootInstanceMetadata', + scope: 'root', + }); } diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index c811a513a8..6bcee4b043 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -85,4 +85,8 @@ export type { UrlReaderServiceSearchResponseFile, } from './UrlReaderService'; export type { BackstageUserInfo, UserInfoService } from './UserInfoService'; +export type { + RootInstanceMetadataService, + RootInstanceMetadataServicePluginInfo, +} from './RootInstanceMetadataService'; export { coreServices } from './coreServices'; diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 7625dc43c4..d8ee338822 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/backend-test-utils +## 1.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-app-api@1.4.0-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.10.0 + +### Minor Changes + +- d57b13b: Added support for Postgres 18 to the available `TestDatabases`. + + Note that the set of _default_ databases to test against for users of the `TestDatabases` class was also updated to include Postgres 14 and 18, instead of 13 and 17. If you need to override this, you can pass in an explicit `ids` argument, for example `ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3']`. + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/backend-app-api@1.3.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-events-node@0.4.17 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/config@1.3.6 + +## 1.10.0-next.2 + +### Patch Changes + +- f3001fd: Tweak some of the mock services to have more precise types +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + +## 1.10.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/backend-app-api@1.3.0-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-events-node@0.4.17-next.1 + ## 1.10.0-next.0 ### Minor Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 7ce66eaa85..cd4f68ead0 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.0-next.0", + "version": "1.10.1-next.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -67,7 +67,7 @@ "@types/qs": "^6.9.6", "better-sqlite3": "^12.0.0", "cookie": "^0.7.0", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.0.0", "keyv": "^5.2.1", "knex": "^3.0.0", @@ -75,8 +75,8 @@ "mysql2": "^3.0.0", "pg": "^8.11.3", "pg-connection-string": "^2.3.0", - "testcontainers": "^10.0.0", - "textextensions": "^5.16.0", + "testcontainers": "^11.9.0", + "text-extensions": "^2.4.0", "uuid": "^11.0.0", "yn": "^4.0.0", "zod": "^3.22.4", diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index e7763329a8..da5002275b 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -35,6 +35,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootHealthService } from '@backstage/backend-plugin-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; +import { RootInstanceMetadataService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; @@ -229,11 +230,7 @@ export namespace mockServices { export function events(): EventsService; // (undocumented) export namespace events { - const factory: () => ServiceFactory< - EventsService, - 'plugin', - 'singleton' | 'multiton' - >; + const factory: () => ServiceFactory; const mock: ( partialImpl?: Partial | undefined, ) => ServiceMock; @@ -319,7 +316,7 @@ export namespace mockServices { const // (undocumented) factory: ( options?: Options | undefined, - ) => ServiceFactory; + ) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -344,6 +341,21 @@ export namespace mockServices { ) => ServiceMock; } // (undocumented) + export function rootInstanceMetadata(): RootInstanceMetadataService; + // (undocumented) + export namespace rootInstanceMetadata { + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + const // (undocumented) + factory: () => ServiceFactory< + RootInstanceMetadataService, + 'root', + 'singleton' + >; + } + // (undocumented) export namespace rootLifecycle { const // (undocumented) factory: () => ServiceFactory; @@ -353,7 +365,7 @@ export namespace mockServices { ) => ServiceMock; } // (undocumented) - export function rootLogger(options?: rootLogger.Options): LoggerService; + export function rootLogger(options?: rootLogger.Options): RootLoggerService; // (undocumented) export namespace rootLogger { // (undocumented) @@ -363,7 +375,7 @@ export namespace mockServices { const // (undocumented) factory: ( options?: Options | undefined, - ) => ServiceFactory; + ) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, diff --git a/packages/backend-test-utils/src/database/postgres.ts b/packages/backend-test-utils/src/database/postgres.ts index e6122b9ab7..d7238cb36a 100644 --- a/packages/backend-test-utils/src/database/postgres.ts +++ b/packages/backend-test-utils/src/database/postgres.ts @@ -77,7 +77,11 @@ export async function startPostgresContainer(image: string): Promise<{ const container = await new GenericContainer(image) .withExposedPorts(5432) - .withEnvironment({ POSTGRES_PASSWORD: password }) + .withEnvironment({ + // Since postgres 18, the default directory changed - so we pin it here + PGDATA: '/var/lib/postgresql/data', + POSTGRES_PASSWORD: password, + }) .withTmpFs({ '/var/lib/postgresql/data': 'rw' }) .start(); diff --git a/packages/backend-test-utils/src/filesystem/MockDirectory.ts b/packages/backend-test-utils/src/filesystem/MockDirectory.ts index a1d616d8e0..f940a4cd7b 100644 --- a/packages/backend-test-utils/src/filesystem/MockDirectory.ts +++ b/packages/backend-test-utils/src/filesystem/MockDirectory.ts @@ -17,7 +17,7 @@ import os from 'os'; import { isChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs-extra'; -import textextensions from 'textextensions'; +import textextensions from 'text-extensions'; import { dirname, extname, diff --git a/packages/backend-test-utils/src/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts index f7d57d3fb1..9e941db3bb 100644 --- a/packages/backend-test-utils/src/services/mockServices.ts +++ b/packages/backend-test-utils/src/services/mockServices.ts @@ -34,7 +34,7 @@ import { DatabaseService, DiscoveryService, HttpAuthService, - LoggerService, + RootInstanceMetadataService, PermissionsService, RootConfigService, SchedulerService, @@ -43,6 +43,7 @@ import { UserInfoService, coreServices, createServiceFactory, + RootLoggerService, } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EventsService, eventsServiceRef } from '@backstage/plugin-events-node'; @@ -76,14 +77,15 @@ function createLoggerMock() { function simpleFactoryWithOptions< TService, TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton', TOptions extends [options?: object] = [], >( - ref: ServiceRef, + ref: ServiceRef, factory: (...options: TOptions) => TService, -): (...options: TOptions) => ServiceFactory { +): (...options: TOptions) => ServiceFactory { const factoryWithOptions = (...options: TOptions) => createServiceFactory({ - service: ref as ServiceRef, + service: ref as ServiceRef, deps: {}, async factory() { return factory(...options); @@ -92,8 +94,8 @@ function simpleFactoryWithOptions< return Object.assign( factoryWithOptions, factoryWithOptions(...([undefined] as unknown as TOptions)), - ) as ServiceFactory & - ((...options: TOptions) => ServiceFactory); + ) as ServiceFactory & + ((...options: TOptions) => ServiceFactory); } /** @@ -177,7 +179,7 @@ export namespace mockServices { })); } - export function rootLogger(options?: rootLogger.Options): LoggerService { + export function rootLogger(options?: rootLogger.Options): RootLoggerService { return MockRootLoggerService.create(options); } export namespace rootLogger { @@ -556,4 +558,19 @@ export namespace mockServices { subscribe: jest.fn(), })); } + + export function rootInstanceMetadata(): RootInstanceMetadataService { + return { + getInstalledPlugins: () => Promise.resolve([]), + }; + } + export namespace rootInstanceMetadata { + export const mock = simpleMock(coreServices.rootInstanceMetadata, () => ({ + getInstalledPlugins: jest.fn(), + })); + export const factory = simpleFactoryWithOptions( + coreServices.rootInstanceMetadata, + rootInstanceMetadata, + ); + } } diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 12e2ac4f1d..622c2329b2 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,144 @@ # example-backend +## 0.0.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.10-next.0 + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@3.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.20.5-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.6-next.0 + - @backstage/plugin-search-backend@2.0.9-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.0 + - @backstage/plugin-app-backend@0.5.9-next.0 + - @backstage/plugin-auth-backend@0.25.7-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.0 + - @backstage/plugin-permission-backend@0.7.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.0 + - @backstage/plugin-permission-node@0.10.7-next.0 + - @backstage/plugin-events-backend@0.5.9-next.0 + - @backstage/plugin-proxy-backend@0.6.9-next.0 + - @backstage/plugin-catalog-backend@3.2.1-next.0 + - @backstage/plugin-devtools-backend@0.5.12-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.0 + - @backstage/plugin-notifications-backend@0.6.1-next.0 + - @backstage/plugin-signals-backend@0.3.11-next.0 + - @backstage/plugin-techdocs-backend@2.1.3-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-permission-common@0.9.3 + +## 0.0.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-catalog-backend@3.2.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.8 + - @backstage/plugin-kubernetes-backend@0.20.4 + - @backstage/plugin-techdocs-backend@2.1.2 + - @backstage/plugin-signals-backend@0.3.10 + - @backstage/plugin-notifications-backend@0.6.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.16 + - @backstage/backend-plugin-api@1.5.0 + - @backstage/plugin-permission-backend@0.7.6 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend@2.0.8 + - @backstage/plugin-auth-backend@0.25.6 + - @backstage/plugin-scaffolder-backend@3.0.1 + - @backstage/plugin-search-backend-node@1.3.17 + - @backstage/plugin-events-backend@0.5.8 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8 + - @backstage/plugin-devtools-backend@0.5.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.6 + - @backstage/plugin-auth-node@0.6.9 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-mcp-actions-backend@0.1.5 + - @backstage/plugin-app-backend@0.5.8 + - @backstage/plugin-auth-backend-module-github-provider@0.3.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.14 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.6 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14 + - @backstage/plugin-permission-node@0.10.6 + - @backstage/plugin-proxy-backend@0.6.8 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16 + - @backstage/plugin-search-backend-module-catalog@0.3.10 + - @backstage/plugin-search-backend-module-explore@0.3.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.8 + +## 0.0.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-backend@0.6.0-next.2 + - @backstage/backend-plugin-api@1.5.0-next.2 + - @backstage/plugin-kubernetes-backend@0.20.4-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.2 + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-scaffolder-backend@3.0.1-next.1 + - @backstage/plugin-search-backend@2.0.8-next.1 + +## 0.0.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.0-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.8-next.1 + - @backstage/plugin-kubernetes-backend@0.20.4-next.1 + - @backstage/plugin-techdocs-backend@2.1.2-next.1 + - @backstage/plugin-signals-backend@0.3.10-next.1 + - @backstage/backend-defaults@0.13.1-next.1 + - @backstage/plugin-notifications-backend@0.5.12-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.16-next.1 + - @backstage/backend-plugin-api@1.5.0-next.1 + - @backstage/plugin-permission-backend@0.7.6-next.1 + - @backstage/plugin-permission-common@0.9.3-next.1 + - @backstage/plugin-search-backend@2.0.8-next.1 + - @backstage/plugin-auth-backend@0.25.6-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.8-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.14-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.6-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.5-next.1 + - @backstage/plugin-scaffolder-backend@3.0.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.8-next.1 + - @backstage/plugin-app-backend@0.5.8-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.9-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.14-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.2-next.1 + - @backstage/plugin-auth-node@0.6.9-next.1 + - @backstage/plugin-devtools-backend@0.5.11-next.1 + - @backstage/plugin-events-backend@0.5.8-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.6-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.14-next.1 + - @backstage/plugin-permission-node@0.10.6-next.1 + - @backstage/plugin-proxy-backend@0.6.8-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.16-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.9-next.1 + - @backstage/plugin-search-backend-node@1.3.17-next.1 + ## 0.0.44-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index d6efe662c7..1d0040389c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.44-next.0", + "version": "0.0.45-next.0", "backstage": { "role": "backend" }, @@ -66,9 +66,9 @@ "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-techdocs-backend": "workspace:^", - "@opentelemetry/auto-instrumentations-node": "^0.61.0", - "@opentelemetry/exporter-prometheus": "^0.54.0", - "@opentelemetry/sdk-node": "^0.54.0", + "@opentelemetry/auto-instrumentations-node": "^0.67.0", + "@opentelemetry/exporter-prometheus": "^0.208.0", + "@opentelemetry/sdk-node": "^0.208.0", "example-app": "link:../app" }, "devDependencies": { diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 541c3bd791..14eb36a3a6 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -15,6 +15,7 @@ */ import { createBackend } from '@backstage/backend-defaults'; +import { rootSystemMetadataServiceFactory } from '@backstage/backend-defaults/alpha'; import { coreServices, createBackendFeatureLoader, @@ -69,7 +70,7 @@ backend.add(searchLoader); backend.add(import('@backstage/plugin-techdocs-backend')); backend.add(import('@backstage/plugin-signals-backend')); backend.add(import('@backstage/plugin-notifications-backend')); -backend.add(import('./instanceMetadata')); +backend.add(rootSystemMetadataServiceFactory); backend.add(import('@backstage/plugin-events-backend-module-google-pubsub')); backend.add(import('@backstage/plugin-mcp-actions-backend')); diff --git a/packages/backend/src/instanceMetadata.ts b/packages/backend/src/instanceMetadata.ts deleted file mode 100644 index 5f835f3117..0000000000 --- a/packages/backend/src/instanceMetadata.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; - -// Example usage of the instance metadata service to log the installed features. -export default createBackendPlugin({ - pluginId: 'instance-metadata-logging', - register(env) { - env.registerInit({ - deps: { - instanceMetadata: instanceMetadataServiceRef, - logger: coreServices.logger, - }, - async init({ instanceMetadata, logger }) { - logger.info( - `Installed features on this instance: ${instanceMetadata - .getInstalledFeatures() - .join(', ')}`, - ); - }, - }); - }, -}); diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 5eb97934f9..9ac3e62d06 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/catalog-client +## 1.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.6 + ## 1.12.1-next.0 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index e5c63b1e0d..fefca00189 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.12.1-next.0", + "version": "1.12.1", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 9d42e9d675..2fb24c4943 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-model +## 1.7.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + ## 1.7.6-next.0 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 93a13b7edf..85c299e941 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.7.6-next.0", + "version": "1.7.6", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md index 4a7880688d..d40c0629f4 100644 --- a/packages/cli-common/CHANGELOG.md +++ b/packages/cli-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/cli-common +## 0.1.16-next.1 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. + +## 0.1.16-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks + ## 0.1.15 ### Patch Changes diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index d8242fd325..ea26ee09e6 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-common", - "version": "0.1.15", + "version": "0.1.16-next.1", "description": "Common functionality used by cli, backend, and create-app", "backstage": { "role": "node-library" @@ -34,8 +34,15 @@ "start": "backstage-cli package start", "test": "backstage-cli package test" }, + "dependencies": { + "@backstage/errors": "workspace:^", + "cross-spawn": "^7.0.3", + "global-agent": "^3.0.0", + "undici": "^7.2.3" + }, "devDependencies": { "@backstage/cli": "workspace:^", - "@types/node": "^20.16.0" + "@types/cross-spawn": "^6.0.2", + "@types/node": "^22.13.14" } } diff --git a/packages/cli-common/report.api.md b/packages/cli-common/report.api.md index 34f56c3f19..175ec3cb2b 100644 --- a/packages/cli-common/report.api.md +++ b/packages/cli-common/report.api.md @@ -3,9 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ChildProcess } from 'child_process'; +import { CustomErrorBase } from '@backstage/errors'; +import { SpawnOptions } from 'child_process'; + // @public export const BACKSTAGE_JSON = 'backstage.json'; +// @public +export function bootstrapEnvProxyAgents(): void; + +// @public +export class ExitCodeError extends CustomErrorBase { + constructor(code: number, command?: string); + // (undocumented) + readonly code: number; +} + // @public export function findPaths(searchDir: string): Paths; @@ -26,4 +40,32 @@ export type Paths = { // @public export type ResolveFunc = (...paths: string[]) => string; + +// @public +export function run(args: string[], options?: RunOptions): RunChildProcess; + +// @public +export function runCheck(args: string[]): Promise; + +// @public +export interface RunChildProcess extends ChildProcess { + waitForExit(): Promise; +} + +// @public +export type RunOnOutput = (data: Buffer) => void; + +// @public +export type RunOptions = Omit & { + env?: Partial; + onStdout?: RunOnOutput; + onStderr?: RunOnOutput; + stdio?: SpawnOptions['stdio']; +}; + +// @public +export function runOutput( + args: string[], + options?: RunOptions, +): Promise; ``` diff --git a/packages/cli-common/src/errors.ts b/packages/cli-common/src/errors.ts new file mode 100644 index 0000000000..07664d2ffb --- /dev/null +++ b/packages/cli-common/src/errors.ts @@ -0,0 +1,34 @@ +/* + * 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 { CustomErrorBase } from '@backstage/errors'; + +/** + * Error thrown when a child process exits with a non-zero code. + * @public + */ +export class ExitCodeError extends CustomErrorBase { + readonly code: number; + + constructor(code: number, command?: string) { + super( + command + ? `Command '${command}' exited with code ${code}` + : `Child exited with code ${code}`, + ); + this.code = code; + } +} diff --git a/packages/cli-common/src/index.ts b/packages/cli-common/src/index.ts index 8809764b43..d11601b044 100644 --- a/packages/cli-common/src/index.ts +++ b/packages/cli-common/src/index.ts @@ -23,3 +23,13 @@ export { findPaths, BACKSTAGE_JSON } from './paths'; export { isChildPath } from './isChildPath'; export type { Paths, ResolveFunc } from './paths'; +export { bootstrapEnvProxyAgents } from './proxyBootstrap'; +export { + run, + runOutput, + runCheck, + type RunChildProcess, + type RunOptions, + type RunOnOutput, +} from './run'; +export { ExitCodeError } from './errors'; diff --git a/packages/cli-common/src/proxyBootstrap.test.ts b/packages/cli-common/src/proxyBootstrap.test.ts new file mode 100644 index 0000000000..cefa1bd36e --- /dev/null +++ b/packages/cli-common/src/proxyBootstrap.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { bootstrapEnvProxyAgents } from './proxyBootstrap'; + +// Avoid mutating the global agents used in other tests +jest.mock('global-agent', () => ({ + bootstrap: jest.fn(), +})); +jest.mock('undici', () => ({ + setGlobalDispatcher: jest.fn(), + EnvHttpProxyAgent: jest.fn(), +})); + +describe('bootstrapEnvProxyAgents', () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + jest.clearAllMocks(); + }); + + it('should bootstrap global-agent if GLOBAL_AGENT_HTTP_PROXY is set', () => { + process.env.GLOBAL_AGENT_HTTP_PROXY = 'http://proxy.example.com'; + + const { bootstrap } = + require('global-agent') as typeof import('global-agent'); + bootstrapEnvProxyAgents(); + + expect(bootstrap).toHaveBeenCalledTimes(1); + }); + + it('should bootstrap global-agent if GLOBAL_AGENT_HTTPS_PROXY is set', () => { + process.env.GLOBAL_AGENT_HTTPS_PROXY = 'https://proxy.example.com'; + + const { bootstrap } = + require('global-agent') as typeof import('global-agent'); + bootstrapEnvProxyAgents(); + + expect(bootstrap).toHaveBeenCalledTimes(1); + }); + + it('should use undici EnvHttpProxyAgent if HTTP_PROXY is set', () => { + process.env.HTTP_PROXY = 'http://proxy.example.com'; + + const { setGlobalDispatcher, EnvHttpProxyAgent } = + require('undici') as typeof import('undici'); + bootstrapEnvProxyAgents(); + + expect(EnvHttpProxyAgent).toHaveBeenCalledTimes(1); + expect(setGlobalDispatcher).toHaveBeenCalledWith( + expect.any(EnvHttpProxyAgent), + ); + }); + + it('should use undici EnvHttpProxyAgent if HTTPS_PROXY is set', () => { + process.env.HTTPS_PROXY = 'https://proxy.example.com'; + + const { setGlobalDispatcher, EnvHttpProxyAgent } = + require('undici') as typeof import('undici'); + bootstrapEnvProxyAgents(); + + expect(EnvHttpProxyAgent).toHaveBeenCalledTimes(1); + expect(setGlobalDispatcher).toHaveBeenCalledWith( + expect.any(EnvHttpProxyAgent), + ); + }); + + it('should not bootstrap global-agent or set undici dispatcher if no proxy is set', () => { + const { bootstrap } = + require('global-agent') as typeof import('global-agent'); + const { setGlobalDispatcher } = + require('undici') as typeof import('undici'); + + bootstrapEnvProxyAgents(); + + expect(bootstrap).not.toHaveBeenCalled(); + expect(setGlobalDispatcher).not.toHaveBeenCalled(); + }); + + it('should respect GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE', () => { + process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = 'CUSTOM_AGENT_'; + process.env.CUSTOM_AGENT_HTTP_PROXY = 'http://proxy.example.com'; + + const { bootstrap } = + require('global-agent') as typeof import('global-agent'); + bootstrapEnvProxyAgents(); + + expect(bootstrap).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/cli-common/src/proxyBootstrap.ts b/packages/cli-common/src/proxyBootstrap.ts new file mode 100644 index 0000000000..b7375b6e55 --- /dev/null +++ b/packages/cli-common/src/proxyBootstrap.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. + */ + +/** + * This function can be called to setup undici and node-fetch Proxy agents. + * + * You can set GLOBAL_AGENT_HTTP(S)_PROXY to configure a proxy to be used in the + * CLIs. + * + * You can also configure a custom namespace by setting + * GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE which will replace the default + * "GLOBAL_AGENT_" env-var prefix. + * + * Make sure to call this function before any other imports. + * + * @public + */ +export function bootstrapEnvProxyAgents() { + // see https://www.npmjs.com/package/global-agent + const globalAgentNamespace = + process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE ?? 'GLOBAL_AGENT_'; + if ( + process.env[`${globalAgentNamespace}HTTP_PROXY`] || + process.env[`${globalAgentNamespace}HTTPS_PROXY`] + ) { + const globalAgent = + require('global-agent') as typeof import('global-agent'); + globalAgent.bootstrap(); + } + + if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) { + const { setGlobalDispatcher, EnvHttpProxyAgent } = + require('undici') as typeof import('undici'); + setGlobalDispatcher(new EnvHttpProxyAgent()); + } +} diff --git a/packages/cli-common/src/run.test.ts b/packages/cli-common/src/run.test.ts new file mode 100644 index 0000000000..01c6c915d6 --- /dev/null +++ b/packages/cli-common/src/run.test.ts @@ -0,0 +1,342 @@ +/* + * 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 { run, runOutput, runCheck } from './run'; +import { ExitCodeError } from './errors'; + +describe('run', () => { + const activeChildren: Array<{ kill: () => void }> = []; + + afterEach(() => { + // Clean up any active child processes + activeChildren.forEach(child => { + try { + child.kill(); + } catch { + // Ignore errors during cleanup + } + }); + activeChildren.length = 0; + jest.restoreAllMocks(); + }); + + describe('run', () => { + it('should throw error for empty args', () => { + expect(() => run([])).toThrow('run requires at least one argument'); + }); + + it('should run a successful command', async () => { + const child = run(['node', '--version']); + activeChildren.push(child); + await expect(child.waitForExit()).resolves.not.toThrow(); + }); + + it('should throw ExitCodeError for non-zero exit code', async () => { + const child = run(['node', '--eval', 'process.exit(1)']); + activeChildren.push(child); + await expect(child.waitForExit()).rejects.toThrow(ExitCodeError); + await expect(child.waitForExit()).rejects.toThrow( + /Command 'node --eval process\.exit\(1\)' exited with code 1/, + ); + }); + + it('should call onStdout callback', async () => { + const stdoutChunks: Buffer[] = []; + const child = run(['node', '--version'], { + onStdout: data => stdoutChunks.push(data), + }); + activeChildren.push(child); + await child.waitForExit(); + expect(stdoutChunks.length).toBeGreaterThan(0); + const output = Buffer.concat(stdoutChunks).toString(); + expect(output).toMatch(/v\d+\.\d+\.\d+/); + }); + + it('should call onStderr callback', async () => { + const stderrChunks: Buffer[] = []; + const child = run(['node', '--eval', 'console.error("test error")'], { + onStderr: data => stderrChunks.push(data), + }); + activeChildren.push(child); + await child.waitForExit(); + expect(stderrChunks.length).toBeGreaterThan(0); + const output = Buffer.concat(stderrChunks).toString(); + expect(output).toContain('test error'); + }); + + it('should use custom stdio', async () => { + const child = run(['node', '--version'], { + stdio: 'pipe', + }); + activeChildren.push(child); + expect(child.stdout).toBeTruthy(); + expect(child.stderr).toBeTruthy(); + await child.waitForExit(); + }); + + it('should use custom env', async () => { + const customEnv = { CUSTOM_VAR: 'test-value' }; + const child = run( + ['node', '--eval', 'console.log(process.env.CUSTOM_VAR)'], + { + env: customEnv, + onStdout: data => { + const output = data.toString(); + expect(output).toContain('test-value'); + }, + }, + ); + activeChildren.push(child); + await child.waitForExit(); + }); + + it('should set FORCE_COLOR in env', async () => { + const child = run( + ['node', '--eval', 'console.log(process.env.FORCE_COLOR)'], + { + onStdout: data => { + const output = data.toString(); + expect(output.trim()).toBe('true'); + }, + }, + ); + activeChildren.push(child); + await child.waitForExit(); + }); + + it('should handle process already exited', async () => { + const child = run(['node', '--version']); + activeChildren.push(child); + // Wait for it to complete + await child.waitForExit(); + // Call waitForExit again - should return immediately + await expect(child.waitForExit()).resolves.not.toThrow(); + }); + + it('should handle multiple simultaneous calls to waitForExit', async () => { + const child = run(['node', '--version']); + activeChildren.push(child); + // Call waitForExit multiple times simultaneously + const [result1, result2, result3] = await Promise.all([ + child.waitForExit(), + child.waitForExit(), + child.waitForExit(), + ]); + // All should resolve successfully + expect(result1).toBeUndefined(); + expect(result2).toBeUndefined(); + expect(result3).toBeUndefined(); + }); + + it('should handle multiple simultaneous calls to waitForExit with error', async () => { + const child = run(['node', '--eval', 'process.exit(1)']); + activeChildren.push(child); + // Call waitForExit multiple times simultaneously + const promises = [ + child.waitForExit(), + child.waitForExit(), + child.waitForExit(), + ]; + // All should reject with the same error + for (const promise of promises) { + await expect(promise).rejects.toThrow(ExitCodeError); + await expect(promise).rejects.toThrow( + /Command 'node --eval process\.exit\(1\)' exited with code 1/, + ); + } + }); + + it('should handle signal handlers cleanup', async () => { + const child = run(['node', '--version']); + activeChildren.push(child); + const originalListeners = process.listenerCount('SIGINT'); + await child.waitForExit(); + // Signal handlers should be cleaned up + expect(process.listenerCount('SIGINT')).toBe(originalListeners); + }); + + it('should kill child process on SIGINT', async () => { + const child = run(['node', '--eval', 'setTimeout(() => {}, 10000)']); + activeChildren.push(child); + const killSpy = jest.spyOn(child, 'kill'); + // Start waiting (this registers signal handlers) + const waitPromise = child.waitForExit(); + // Simulate SIGINT + process.emit('SIGINT' as any, 'SIGINT'); + // Give it a moment + await new Promise(resolve => setTimeout(resolve, 100)); + expect(killSpy).toHaveBeenCalled(); + killSpy.mockRestore(); + // Clean up + child.kill(); + // Wait for cleanup + try { + await Promise.race([ + waitPromise, + new Promise(resolve => setTimeout(resolve, 100)), + ]); + } catch { + // Expected to fail + } + }); + + it('should kill child process on SIGTERM', async () => { + const child = run(['node', '--eval', 'setTimeout(() => {}, 10000)']); + activeChildren.push(child); + const killSpy = jest.spyOn(child, 'kill'); + // Start waiting (this registers signal handlers) + const waitPromise = child.waitForExit(); + // Simulate SIGTERM + process.emit('SIGTERM' as any, 'SIGTERM'); + // Give it a moment + await new Promise(resolve => setTimeout(resolve, 100)); + expect(killSpy).toHaveBeenCalled(); + killSpy.mockRestore(); + // Clean up + child.kill(); + // Wait for cleanup + try { + await Promise.race([ + waitPromise, + new Promise(resolve => setTimeout(resolve, 100)), + ]); + } catch { + // Expected to fail + } + }); + + it('should not kill already killed process on signal', async () => { + const child = run(['node', '--version']); + activeChildren.push(child); + await child.waitForExit(); + const killSpy = jest.spyOn(child, 'kill'); + // Simulate SIGINT after process has exited + process.emit('SIGINT' as any, 'SIGINT'); + await new Promise(resolve => setTimeout(resolve, 100)); + // Should not be called since process already exited + expect(killSpy).not.toHaveBeenCalled(); + killSpy.mockRestore(); + }); + + it('should handle process error', async () => { + const child = run(['nonexistent-command-12345']); + activeChildren.push(child); + await expect(child.waitForExit()).rejects.toThrow(); + }); + }); + + describe('runOutput', () => { + it('should throw error for empty args', async () => { + await expect(runOutput([])).rejects.toThrow( + 'runOutput requires at least one argument', + ); + }); + + it('should return stdout', async () => { + const output = await runOutput([ + 'node', + '--eval', + 'console.log("test output")', + ]); + expect(output).toBe('test output'); + }); + + it('should trim output', async () => { + const output = await runOutput([ + 'node', + '--eval', + 'console.log(" test output ")', + ]); + expect(output).toBe('test output'); + }); + + it('should attach stdout to error on failure', async () => { + let error: Error | undefined; + try { + await runOutput([ + 'node', + '--eval', + 'console.log("stdout before error"); process.exit(1)', + ]); + } catch (err) { + error = err; + } + expect(error).toBeInstanceOf(ExitCodeError); + expect((error as Error & { stdout?: string }).stdout).toContain( + 'stdout before error', + ); + }); + + it('should attach stderr to error on failure', async () => { + let error: Error | undefined; + try { + await runOutput([ + 'node', + '--eval', + 'console.error("stderr error"); process.exit(1)', + ]); + } catch (err) { + error = err; + } + expect(error).toBeInstanceOf(ExitCodeError); + expect((error as Error & { stderr?: string }).stderr).toContain( + 'stderr error', + ); + }); + + it('should call custom onStdout callback', async () => { + const customChunks: Buffer[] = []; + const output = await runOutput( + ['node', '--eval', 'console.log("test")'], + { + onStdout: data => customChunks.push(data), + }, + ); + expect(output).toBe('test'); + expect(customChunks.length).toBeGreaterThan(0); + }); + + it('should call custom onStderr callback', async () => { + const customChunks: Buffer[] = []; + await runOutput( + ['node', '--eval', 'console.error("error"); console.log("ok")'], + { + onStderr: data => customChunks.push(data), + }, + ); + expect(customChunks.length).toBeGreaterThan(0); + const errorOutput = Buffer.concat(customChunks).toString(); + expect(errorOutput).toContain('error'); + }); + }); + + describe('runCheck', () => { + it('should return true for successful command', async () => { + const result = await runCheck(['node', '--version']); + expect(result).toBe(true); + }); + + it('should return false for failed command', async () => { + const result = await runCheck(['node', '--eval', 'process.exit(1)']); + expect(result).toBe(false); + }); + + it('should return false for nonexistent command', async () => { + const result = await runCheck(['nonexistent-command-12345']); + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/cli-common/src/run.ts b/packages/cli-common/src/run.ts new file mode 100644 index 0000000000..9bdfbcd1f6 --- /dev/null +++ b/packages/cli-common/src/run.ts @@ -0,0 +1,219 @@ +/* + * 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 { ChildProcess, SpawnOptions } from 'child_process'; +import spawn from 'cross-spawn'; +import { ExitCodeError } from './errors'; +import { assertError } from '@backstage/errors'; + +/** + * Callback function that can be used to receive stdout or stderr data from a child process. + * + * @public + */ +export type RunOnOutput = (data: Buffer) => void; + +/** + * Options for running a child process with {@link run} or related functions. + * + * @public + */ +export type RunOptions = Omit & { + env?: Partial; + onStdout?: RunOnOutput; + onStderr?: RunOnOutput; + stdio?: SpawnOptions['stdio']; +}; + +/** + * Child process handle returned by {@link run}. + * + * @public + */ +export interface RunChildProcess extends ChildProcess { + /** + * Waits for the child process to exit. + * + * @remarks + * + * Resolves when the process exits successfully (exit code 0) or is terminated by a signal. + * If the process exits with a non-zero exit code, the promise is rejected with an {@link ExitCodeError}. + * + * @returns A promise that resolves when the process exits successfully or is terminated by a signal, or rejects on error. + */ + waitForExit(): Promise; +} + +/** + * Runs a command and returns a child process handle. + * + * @public + */ +export function run(args: string[], options: RunOptions = {}): RunChildProcess { + if (args.length === 0) { + throw new Error('run requires at least one argument'); + } + + const [name, ...cmdArgs] = args; + + const { onStdout, onStderr, stdio: customStdio, ...spawnOptions } = options; + const env: NodeJS.ProcessEnv = { + ...process.env, + FORCE_COLOR: 'true', + ...(options.env ?? {}), + }; + + const stdio = + customStdio ?? + ([ + 'inherit', + onStdout ? 'pipe' : 'inherit', + onStderr ? 'pipe' : 'inherit', + ] as ('inherit' | 'pipe')[]); + + const child = spawn(name, cmdArgs, { + ...spawnOptions, + stdio, + env, + }) as RunChildProcess; + + if (onStdout && child.stdout) { + child.stdout.on('data', onStdout); + } + if (onStderr && child.stderr) { + child.stderr.on('data', onStderr); + } + + const commandName = args.join(' '); + + let waitPromise: Promise | undefined; + + child.waitForExit = async (): Promise => { + if (waitPromise) { + return waitPromise; + } + + waitPromise = new Promise((resolve, reject) => { + if (typeof child.exitCode === 'number') { + if (child.exitCode) { + reject(new ExitCodeError(child.exitCode, commandName)); + } else { + resolve(); + } + return; + } + + function onError(error: Error) { + cleanup(); + reject(error); + } + + function onExit(code: number | null) { + cleanup(); + if (code) { + reject(new ExitCodeError(code, commandName)); + } else { + resolve(); + } + } + + function onSignal() { + if (!child.killed && child.exitCode === null) { + child.kill(); + } + } + + function cleanup() { + for (const signal of ['SIGINT', 'SIGTERM'] as const) { + process.removeListener(signal, onSignal); + } + child.removeListener('error', onError); + child.removeListener('exit', onExit); + } + + child.once('error', onError); + child.once('exit', onExit); + + for (const signal of ['SIGINT', 'SIGTERM'] as const) { + process.addListener(signal, onSignal); + } + }); + + return waitPromise; + }; + + return child; +} + +/** + * Runs a command and returns the stdout. + * + * @remarks + * + * On error, both stdout and stderr are attached to the error object as properties. + * + * @public + */ +export async function runOutput( + args: string[], + options?: RunOptions, +): Promise { + const stdoutChunks: Buffer[] = []; + const stderrChunks: Buffer[] = []; + + if (args.length === 0) { + throw new Error('runOutput requires at least one argument'); + } + + try { + await run(args, { + ...options, + onStdout: data => { + stdoutChunks.push(data); + options?.onStdout?.(data); + }, + onStderr: data => { + stderrChunks.push(data); + options?.onStderr?.(data); + }, + }).waitForExit(); + + return Buffer.concat(stdoutChunks).toString().trim(); + } catch (error) { + assertError(error); + + (error as Error & { stdout?: string }).stdout = + Buffer.concat(stdoutChunks).toString(); + (error as Error & { stderr?: string }).stderr = + Buffer.concat(stderrChunks).toString(); + + throw error; + } +} + +/** + * Runs a command and returns true if it exits with code 0, false otherwise. + * + * @public + */ +export async function runCheck(args: string[]): Promise { + try { + await run(args).waitForExit(); + return true; + } catch { + return false; + } +} diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md index 7921933482..4c4005c0c9 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/cli-node +## 0.2.16-next.1 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.2.15 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + ## 0.2.15-next.0 ### Patch Changes diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index abd7c959a7..dcedc6b76c 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-node", - "version": "0.2.15-next.0", + "version": "0.2.16-next.1", "description": "Node.js library for Backstage CLIs", "backstage": { "role": "node-library" diff --git a/packages/cli-node/src/git/GitUtils.ts b/packages/cli-node/src/git/GitUtils.ts index 7b87487c56..7edb3581d6 100644 --- a/packages/cli-node/src/git/GitUtils.ts +++ b/packages/cli-node/src/git/GitUtils.ts @@ -15,23 +15,27 @@ */ import { assertError, ForwardedError } from '@backstage/errors'; -import { execFile, paths } from '../util'; +import { paths } from '../paths'; +import { runOutput } from '@backstage/cli-common'; /** * Run a git command, trimming the output splitting it into lines. */ export async function runGit(...args: string[]) { try { - const { stdout } = await execFile('git', args, { - shell: true, + const stdout = await runOutput(['git', ...args], { cwd: paths.targetRoot, }); return stdout.trim().split(/\r\n|\r|\n/); } catch (error) { assertError(error); - if (error.stderr || typeof error.code === 'number') { - const stderr = (error.stderr as undefined | Buffer)?.toString('utf8'); - const msg = stderr?.trim() ?? `with exit code ${error.code}`; + if ( + 'code' in error && + typeof (error as { code?: number }).code === 'number' + ) { + const code = (error as { code?: number }).code; + const stderr = (error as { stderr?: string }).stderr; + const msg = stderr?.trim() ?? `with exit code ${code}`; throw new Error(`git ${args[0]} failed, ${msg}`); } throw new ForwardedError('Unknown execution error', error); @@ -83,10 +87,8 @@ export class GitUtils { // silently fall back to using the ref directly if merge base is not available } - const { stdout } = await execFile('git', ['show', `${showRef}:${path}`], { - shell: true, + const stdout = await runOutput(['git', 'show', `${showRef}:${path}`], { cwd: paths.targetRoot, - maxBuffer: 1024 * 1024 * 50, }); return stdout; } diff --git a/packages/cli-node/src/monorepo/PackageGraph.test.ts b/packages/cli-node/src/monorepo/PackageGraph.test.ts index e2378b91c0..9bc9cbf72b 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.test.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.test.ts @@ -23,7 +23,7 @@ import { GitUtils } from '../git'; const mockListChangedFiles = jest.spyOn(GitUtils, 'listChangedFiles'); const mockReadFileAtRef = jest.spyOn(GitUtils, 'readFileAtRef'); -jest.mock('../util', () => ({ +jest.mock('../paths', () => ({ paths: { targetRoot: '/', resolveTargetRoot: (...paths: string[]) => resolvePath('/', ...paths), diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 2f82af0808..76fbe9ba4f 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -16,7 +16,7 @@ import path from 'path'; import { getPackages, Package } from '@manypkg/get-packages'; -import { paths } from '../util'; +import { paths } from '../paths'; import { PackageRole } from '../roles'; import { GitUtils } from '../git'; import { Lockfile } from './Lockfile'; diff --git a/packages/cli-node/src/monorepo/isMonoRepo.ts b/packages/cli-node/src/monorepo/isMonoRepo.ts index f47ac4b9ad..da78c8dd53 100644 --- a/packages/cli-node/src/monorepo/isMonoRepo.ts +++ b/packages/cli-node/src/monorepo/isMonoRepo.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { paths } from '../util'; +import { paths } from '../paths'; import fs from 'fs-extra'; /** diff --git a/packages/cli-node/src/monorepo/isMonorepo.test.ts b/packages/cli-node/src/monorepo/isMonorepo.test.ts index 30c2d3ccbc..de0dae0893 100644 --- a/packages/cli-node/src/monorepo/isMonorepo.test.ts +++ b/packages/cli-node/src/monorepo/isMonorepo.test.ts @@ -19,7 +19,7 @@ import { createMockDirectory } from '@backstage/backend-test-utils'; const mockDir = createMockDirectory(); -jest.mock('../util', () => ({ +jest.mock('../paths', () => ({ paths: { resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args) }, })); diff --git a/packages/cli-node/src/pacman/PackageManager.test.ts b/packages/cli-node/src/pacman/PackageManager.test.ts index 3997da554c..dd35e74902 100644 --- a/packages/cli-node/src/pacman/PackageManager.test.ts +++ b/packages/cli-node/src/pacman/PackageManager.test.ts @@ -21,8 +21,8 @@ import { withLogCollector } from '@backstage/test-utils'; const mockDir = createMockDirectory(); -jest.mock('../util', () => ({ - ...jest.requireActual('../util'), +jest.mock('../paths', () => ({ + ...jest.requireActual('../paths'), paths: { resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args) }, })); diff --git a/packages/cli-node/src/pacman/PackageManager.ts b/packages/cli-node/src/pacman/PackageManager.ts index fc0aaa2c97..44c0849705 100644 --- a/packages/cli-node/src/pacman/PackageManager.ts +++ b/packages/cli-node/src/pacman/PackageManager.ts @@ -16,7 +16,8 @@ import { Yarn } from './yarn'; import { Lockfile } from './Lockfile'; -import { SpawnOptionsPartialEnv, paths } from '../util'; +import { paths } from '../paths'; +import { RunOptions } from '@backstage/cli-common'; import fs from 'fs-extra'; /** @@ -55,7 +56,7 @@ export interface PackageManager { getMonorepoPackages(): Promise; /** Uses the package manager to run a command in the repo. */ - run(args: string[], options?: SpawnOptionsPartialEnv): Promise; + run(args: string[], options?: RunOptions): Promise; /** * Executes the package manager's pack command to bundle the repo into an diff --git a/packages/cli-node/src/pacman/yarn/Yarn.test.ts b/packages/cli-node/src/pacman/yarn/Yarn.test.ts index 9584ef54b2..9bf866b821 100644 --- a/packages/cli-node/src/pacman/yarn/Yarn.test.ts +++ b/packages/cli-node/src/pacman/yarn/Yarn.test.ts @@ -19,8 +19,8 @@ import { Yarn } from './Yarn'; const mockDir = createMockDirectory(); -jest.mock('../../util', () => ({ - ...jest.requireActual('../../util'), +jest.mock('../../paths', () => ({ + ...jest.requireActual('../../paths'), paths: { resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args) }, })); diff --git a/packages/cli-node/src/pacman/yarn/Yarn.ts b/packages/cli-node/src/pacman/yarn/Yarn.ts index 0a2b8d55ea..a6544344ca 100644 --- a/packages/cli-node/src/pacman/yarn/Yarn.ts +++ b/packages/cli-node/src/pacman/yarn/Yarn.ts @@ -23,7 +23,8 @@ import { PackageInfo, PackageManager } from '../PackageManager'; import { Lockfile } from '../Lockfile'; import { YarnVersion } from './types'; import fs from 'fs-extra'; -import { paths, run, execFile, SpawnOptionsPartialEnv } from '../../util'; +import { paths } from '../../paths'; +import { run, runOutput, RunOptions } from '@backstage/cli-common'; export class Yarn implements PackageManager { constructor(private readonly yarnVersion: YarnVersion) {} @@ -63,8 +64,8 @@ export class Yarn implements PackageManager { }); } - async run(args: string[], options?: SpawnOptionsPartialEnv) { - await run('yarn', args, options); + async run(args: string[], options?: RunOptions) { + await run(['yarn', ...args], options).waitForExit(); } async fetchPackageInfo(): Promise { @@ -98,8 +99,7 @@ function detectYarnVersion(dir?: string): Promise { const promise = Promise.resolve().then(async () => { try { - const { stdout } = await execFile('yarn', ['--version'], { - shell: true, + const stdout = await runOutput(['yarn', '--version'], { cwd, }); const versionString = stdout.trim(); @@ -109,9 +109,6 @@ function detectYarnVersion(dir?: string): Promise { return { version: versionString, codename }; } catch (error) { assertError(error); - if ('stderr' in error) { - process.stderr.write(error.stderr as Buffer); - } throw new ForwardedError('Failed to determine yarn version', error); } }); diff --git a/packages/cli-node/src/paths.ts b/packages/cli-node/src/paths.ts new file mode 100644 index 0000000000..2c658c27b3 --- /dev/null +++ b/packages/cli-node/src/paths.ts @@ -0,0 +1,20 @@ +/* + * 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 { findPaths } from '@backstage/cli-common'; + +/* eslint-disable-next-line no-restricted-syntax */ +export const paths = findPaths(__dirname); diff --git a/packages/cli-node/src/util.ts b/packages/cli-node/src/util.ts deleted file mode 100644 index eaf05855b1..0000000000 --- a/packages/cli-node/src/util.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ChildProcess, - execFile as execFileCb, - spawn, - SpawnOptions, -} from 'child_process'; -import { promisify } from 'util'; -import { findPaths } from '@backstage/cli-common'; -import { ExitCodeError } from './errors'; - -export const execFile = promisify(execFileCb); - -/* eslint-disable-next-line no-restricted-syntax */ -export const paths = findPaths(__dirname); - -/** - * A function that can be used to log data from a child process - * - * @public - */ -export type LogFunc = (data: Buffer) => void; - -/** - * Options for running a child process - * - * @public - */ -export type SpawnOptionsPartialEnv = Omit & { - env?: Partial; - // Pipe stdout to this log function - stdoutLogFunc?: LogFunc; - // Pipe stderr to this log function - stderrLogFunc?: LogFunc; -}; - -// Runs a child command, returning a promise that is only resolved if the child exits with code 0. -export async function run( - name: string, - args: string[] = [], - options: SpawnOptionsPartialEnv = {}, -) { - const { stdoutLogFunc, stderrLogFunc } = options; - const env: NodeJS.ProcessEnv = { - ...process.env, - FORCE_COLOR: 'true', - ...(options.env ?? {}), - }; - - const stdio = [ - 'inherit', - stdoutLogFunc ? 'pipe' : 'inherit', - stderrLogFunc ? 'pipe' : 'inherit', - ] as ('inherit' | 'pipe')[]; - - const child = spawn(name, args, { - stdio, - shell: true, - ...options, - env, - }); - - if (stdoutLogFunc && child.stdout) { - child.stdout.on('data', stdoutLogFunc); - } - if (stderrLogFunc && child.stderr) { - child.stderr.on('data', stderrLogFunc); - } - - await waitForExit(child, name); -} - -async function waitForExit( - child: ChildProcess & { exitCode: number | null }, - name?: string, -): Promise { - if (typeof child.exitCode === 'number') { - if (child.exitCode) { - throw new ExitCodeError(child.exitCode, name); - } - return; - } - - await new Promise((resolve, reject) => { - child.once('error', error => reject(error)); - child.once('exit', code => { - if (code) { - reject(new ExitCodeError(code, name)); - } else { - resolve(); - } - }); - }); -} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 5c18ed6c8a..9cee8632cd 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/cli +## 0.34.6-next.1 + +### Patch Changes + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## 0.34.6-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config@1.3.6 + - @backstage/release-manifests@0.0.13 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/types@1.2.2 + +## 0.34.5 + +### Patch Changes + +- fc7cbfc: The templates executed with the `yarn new` command now supports templating filenames. +- da19cb5: Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. +- fc7cbfc: Added a template for the `yarn new` command to create an catalog entity provider. To add this template to an explicit list in the root `package.json`, use `@backstage/cli/templates/catalog-provider-module`. +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 279279c: Fixes an issue where using the `backstage-cli new --scope` command with a scope that already includes the `@` symbol (e.g., `@backstage-community`) would result in a double `@@` prefix in the generated package name, causing invalid `package.json` files. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/eslint-plugin@0.2.0 + - @backstage/config-loader@1.10.6 + - @backstage/config@1.3.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## 0.34.5-next.1 + +### Patch Changes + +- da19cb5: Fix inconsistent behavior in the `new` command for the `@internal` scope: it now consistently defaults to the `backstage-plugin-` infix whether the `--scope` option is not set or it's set to `internal`. +- b2bef92: Convert all enums to erasable-syntax compliant patterns + ## 0.34.5-next.0 ### Patch Changes diff --git a/packages/cli/config/nodeTransform.cjs b/packages/cli/config/nodeTransform.cjs index f54527b4a7..54984b74a4 100644 --- a/packages/cli/config/nodeTransform.cjs +++ b/packages/cli/config/nodeTransform.cjs @@ -61,7 +61,7 @@ addHook( ignoreDynamic: true, }, jsc: { - target: 'es2022', + target: 'es2023', parser: { syntax: 'typescript', }, diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 7bf0473bb8..7e00eff9f6 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -277,7 +277,7 @@ export async function load(url, context, nextLoad) { exportInteropAnnotation: true, }, jsc: { - target: 'es2022', + target: 'es2023', parser: { syntax: 'typescript', }, diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index ac5e62b52e..7648e0238a 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -12,9 +12,9 @@ "incremental": true, "isolatedModules": true, "jsx": "react", - "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2022"], - "module": "ESNext", - "moduleResolution": "node", + "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2023"], + "module": "ES2020", + "moduleResolution": "bundler", "noEmit": false, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, @@ -33,7 +33,7 @@ "strictNullChecks": true, "strictPropertyInitialization": true, "stripInternal": true, - "target": "ES2022", + "target": "ES2023", "types": ["node", "jest", "webpack-env"], "useDefineForClassFields": true } diff --git a/packages/cli/package.json b/packages/cli/package.json index a3ed006a3d..be94e4e817 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.34.5-next.0", + "version": "0.34.6-next.1", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -88,7 +88,7 @@ "cross-spawn": "^7.0.3", "css-loader": "^6.5.1", "ctrlc-windows": "^2.1.0", - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "eslint": "^8.6.0", "eslint-config-prettier": "^9.0.0", "eslint-formatter-friendly": "^7.0.0", @@ -100,7 +100,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-unused-imports": "^4.1.4", "eslint-rspack-plugin": "^4.2.1", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "glob": "^7.1.7", @@ -167,14 +167,14 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/theme": "workspace:^", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.0", "@types/cross-spawn": "^6.0.2", "@types/ejs": "^3.1.3", "@types/express": "^4.17.6", "@types/fs-extra": "^11.0.0", "@types/http-proxy": "^1.17.4", "@types/inquirer": "^8.1.3", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/npm-packlist": "^3.0.0", "@types/recursive-readdir": "^2.2.0", "@types/rollup-plugin-peer-deps-external": "^2.2.0", @@ -192,18 +192,18 @@ "msw": "^1.0.0", "nodemon": "^3.0.1", "terser-webpack-plugin": "^5.1.3", - "webpack": "~5.96.0", + "webpack": "~5.103.0", "webpack-dev-server": "^5.0.0" }, "peerDependencies": { "@module-federation/enhanced": "^0.9.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "@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", "mini-css-extract-plugin": "^2.4.2", "terser-webpack-plugin": "^5.1.3", - "webpack": "~5.96.0", + "webpack": "~5.103.0", "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { diff --git a/packages/cli/src/lib/run.ts b/packages/cli/src/lib/run.ts deleted file mode 100644 index e8602fcb18..0000000000 --- a/packages/cli/src/lib/run.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - SpawnOptions, - spawn, - ChildProcess, - execFile as execFileCb, -} from 'child_process'; -import { ExitCodeError } from './errors'; -import { promisify } from 'util'; -import { assertError, ForwardedError } from '@backstage/errors'; - -export const execFile = promisify(execFileCb); - -type LogFunc = (data: Buffer) => void; - -type SpawnOptionsPartialEnv = Omit & { - env?: Partial; - // Pipe stdout to this log function - stdoutLogFunc?: LogFunc; - // Pipe stderr to this log function - stderrLogFunc?: LogFunc; -}; - -// Runs a child command, returning a promise that is only resolved if the child exits with code 0. -export async function run( - name: string, - args: string[] = [], - options: SpawnOptionsPartialEnv = {}, -) { - const { stdoutLogFunc, stderrLogFunc } = options; - const env: NodeJS.ProcessEnv = { - ...process.env, - FORCE_COLOR: 'true', - ...(options.env ?? {}), - }; - - const stdio = [ - 'inherit', - stdoutLogFunc ? 'pipe' : 'inherit', - stderrLogFunc ? 'pipe' : 'inherit', - ] as ('inherit' | 'pipe')[]; - - const child = spawn(name, args, { - stdio, - shell: true, - ...options, - env, - }); - - if (stdoutLogFunc && child.stdout) { - child.stdout.on('data', stdoutLogFunc); - } - if (stderrLogFunc && child.stderr) { - child.stderr.on('data', stderrLogFunc); - } - - await waitForExit(child, name); -} - -export async function runPlain(cmd: string, ...args: string[]) { - try { - const { stdout } = await execFile(cmd, args, { shell: true }); - return stdout.trim(); - } catch (error) { - assertError(error); - if ('stderr' in error) { - process.stderr.write(error.stderr as Buffer); - } - if (typeof error.code === 'number') { - throw new ExitCodeError(error.code, [cmd, ...args].join(' ')); - } - throw new ForwardedError('Unknown execution error', error); - } -} - -export async function runCheck(cmd: string, ...args: string[]) { - try { - await execFile(cmd, args, { shell: true }); - return true; - } catch (error) { - return false; - } -} - -export async function waitForExit( - child: ChildProcess & { exitCode: number | null }, - name?: string, -): Promise { - if (typeof child.exitCode === 'number') { - if (child.exitCode) { - throw new ExitCodeError(child.exitCode, name); - } - return; - } - - await new Promise((resolve, reject) => { - child.once('error', error => reject(error)); - child.once('exit', code => { - if (code) { - reject(new ExitCodeError(code, name)); - } else { - resolve(); - } - }); - }); -} diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index de6e7e1ed2..e823ce8769 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -14,16 +14,17 @@ * limitations under the License. */ -import * as runObj from '../run'; +import * as runObj from '@backstage/cli-common'; import * as yarn from './yarn'; import { fetchPackageInfo, mapDependencies } from './packages'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { NotFoundError } from '@backstage/errors'; -jest.mock('../run', () => { +jest.mock('@backstage/cli-common', () => { + const actual = jest.requireActual('@backstage/cli-common'); return { - run: jest.fn(), - execFile: jest.fn(), + ...actual, + runOutput: jest.fn(), }; }); @@ -39,42 +40,40 @@ describe('fetchPackageInfo', () => { }); it('should forward info for yarn classic', async () => { - jest.spyOn(runObj, 'execFile').mockResolvedValue({ - stdout: `{"type":"inspect","data":{"the":"data"}}`, - stderr: '', - }); + jest + .spyOn(runObj, 'runOutput') + .mockResolvedValue(`{"type":"inspect","data":{"the":"data"}}`); jest.spyOn(yarn, 'detectYarnVersion').mockResolvedValue('classic'); await expect(fetchPackageInfo('my-package')).resolves.toEqual({ the: 'data', }); - expect(runObj.execFile).toHaveBeenCalledWith( + expect(runObj.runOutput).toHaveBeenCalledWith([ 'yarn', - ['info', '--json', 'my-package'], - { shell: true }, - ); + 'info', + '--json', + 'my-package', + ]); }); it('should forward info for yarn berry', async () => { - jest - .spyOn(runObj, 'execFile') - .mockResolvedValue({ stdout: `{"the":"data"}`, stderr: '' }); + jest.spyOn(runObj, 'runOutput').mockResolvedValue(`{"the":"data"}`); jest.spyOn(yarn, 'detectYarnVersion').mockResolvedValue('berry'); await expect(fetchPackageInfo('my-package')).resolves.toEqual({ the: 'data', }); - expect(runObj.execFile).toHaveBeenCalledWith( + expect(runObj.runOutput).toHaveBeenCalledWith([ 'yarn', - ['npm', 'info', '--json', 'my-package'], - { shell: true }, - ); + 'npm', + 'info', + '--json', + 'my-package', + ]); }); it('should throw if no info with yarn classic', async () => { - jest - .spyOn(runObj, 'execFile') - .mockResolvedValue({ stdout: '', stderr: '' }); + jest.spyOn(runObj, 'runOutput').mockResolvedValue(''); jest.spyOn(yarn, 'detectYarnVersion').mockResolvedValue('classic'); await expect(fetchPackageInfo('my-package')).rejects.toThrow( @@ -83,9 +82,10 @@ describe('fetchPackageInfo', () => { }); it('should throw if no info with yarn berry', async () => { - jest - .spyOn(runObj, 'execFile') - .mockRejectedValue({ stdout: 'bla bla bla Response Code: 404 bla bla' }); + const error = new Error('Command failed'); + (error as Error & { stdout?: string }).stdout = + 'bla bla bla Response Code: 404 bla bla'; + jest.spyOn(runObj, 'runOutput').mockRejectedValue(error); jest.spyOn(yarn, 'detectYarnVersion').mockResolvedValue('berry'); await expect(fetchPackageInfo('my-package')).rejects.toThrow( diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 223f6b8aab..11f1ba3041 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -17,7 +17,7 @@ import { minimatch } from 'minimatch'; import { getPackages } from '@manypkg/get-packages'; import { detectYarnVersion } from './yarn'; -import { execFile } from '../run'; +import { runOutput } from '@backstage/cli-common'; import { NotFoundError } from '@backstage/errors'; const DEP_TYPES = [ @@ -54,11 +54,7 @@ export async function fetchPackageInfo( const cmd = yarnVersion === 'classic' ? ['info'] : ['npm', 'info']; try { - const { stdout: output } = await execFile( - 'yarn', - [...cmd, '--json', name], - { shell: true }, - ); + const output = await runOutput(['yarn', ...cmd, '--json', name]); if (!output) { throw new NotFoundError( @@ -81,7 +77,12 @@ export async function fetchPackageInfo( throw error; } - if (error?.stdout.includes('Response Code: 404')) { + if ( + error instanceof Error && + 'stdout' in error && + typeof error.stdout === 'string' && + error.stdout.includes('Response Code: 404') + ) { throw new NotFoundError( `No package information found for package ${name}`, ); diff --git a/packages/cli/src/lib/versioning/yarn.ts b/packages/cli/src/lib/versioning/yarn.ts index b6c0383bc2..908ddca949 100644 --- a/packages/cli/src/lib/versioning/yarn.ts +++ b/packages/cli/src/lib/versioning/yarn.ts @@ -15,10 +15,7 @@ */ import { assertError, ForwardedError } from '@backstage/errors'; -import { execFile as execFileCb } from 'child_process'; -import { promisify } from 'util'; - -const execFile = promisify(execFileCb); +import { runOutput } from '@backstage/cli-common'; const versions = new Map>(); @@ -30,16 +27,12 @@ export function detectYarnVersion(dir?: string): Promise<'classic' | 'berry'> { const promise = Promise.resolve().then(async () => { try { - const { stdout } = await execFile('yarn', ['--version'], { - shell: true, + const stdout = await runOutput(['yarn', '--version'], { cwd, }); return stdout.trim().startsWith('1.') ? 'classic' : 'berry'; } catch (error) { assertError(error); - if ('stderr' in error) { - process.stderr.write(error.stderr as Buffer); - } throw new ForwardedError('Failed to determine yarn version', error); } }); diff --git a/packages/cli/src/modules/build/lib/builder/config.ts b/packages/cli/src/modules/build/lib/builder/config.ts index 8add3559f4..3e360b645c 100644 --- a/packages/cli/src/modules/build/lib/builder/config.ts +++ b/packages/cli/src/modules/build/lib/builder/config.ts @@ -259,7 +259,7 @@ export async function makeRollupConfigs( json(), yaml(), esbuild({ - target: 'ES2022', + target: 'ES2023', minify: options.minify, }), ], diff --git a/packages/cli/src/modules/build/lib/builder/types.ts b/packages/cli/src/modules/build/lib/builder/types.ts index 937bd157c9..f80d0d1306 100644 --- a/packages/cli/src/modules/build/lib/builder/types.ts +++ b/packages/cli/src/modules/build/lib/builder/types.ts @@ -13,13 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ import { BackstagePackage, BackstagePackageJson } from '@backstage/cli-node'; -export enum Output { - esm, - cjs, - types, +export const Output = { + esm: 0, + cjs: 1, + types: 2, +} as const; + +/** + * @public + */ +export type Output = (typeof Output)[keyof typeof Output]; + +/** + * @public + */ +export namespace Output { + export type esm = typeof Output.esm; + export type cjs = typeof Output.cjs; + export type types = typeof Output.types; } export type BuildOptions = { diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index da444dff15..284d49d10a 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -29,7 +29,7 @@ import { paths as cliPaths } from '../../../../lib/paths'; import fs from 'fs-extra'; import { optimization as optimizationConfig } from './optimization'; import pickBy from 'lodash/pickBy'; -import { runPlain } from '../../../../lib/run'; +import { runOutput } from '@backstage/cli-common'; import { transforms } from './transforms'; import { version } from '../../../../lib/version'; import yn from 'yn'; @@ -78,14 +78,14 @@ async function readBuildInfo() { let commit: string | undefined; try { - commit = await runPlain('git', 'rev-parse', 'HEAD'); + commit = await runOutput(['git', 'rev-parse', 'HEAD']); } catch (error) { // ignore, see below } let gitVersion: string | undefined; try { - gitVersion = await runPlain('git', 'describe', '--always'); + gitVersion = await runOutput(['git', 'describe', '--always']); } catch (error) { // ignore, see below } diff --git a/packages/cli/src/modules/build/lib/bundler/optimization.ts b/packages/cli/src/modules/build/lib/bundler/optimization.ts index 63319ebec6..8d0eea5b88 100644 --- a/packages/cli/src/modules/build/lib/bundler/optimization.ts +++ b/packages/cli/src/modules/build/lib/bundler/optimization.ts @@ -34,13 +34,13 @@ export const optimization = ( minimize: !isDev, minimizer: [ new MinifyPlugin({ - target: 'ES2022', + target: 'ES2023', format: 'iife', exclude: 'remoteEntry.js', }), // Avoid iife wrapping of module federation remote entry as it breaks the variable assignment new MinifyPlugin({ - target: 'ES2022', + target: 'ES2023', format: undefined, include: 'remoteEntry.js', }), diff --git a/packages/cli/src/modules/build/lib/bundler/transforms.ts b/packages/cli/src/modules/build/lib/bundler/transforms.ts index 64e33b220e..ecf6e20751 100644 --- a/packages/cli/src/modules/build/lib/bundler/transforms.ts +++ b/packages/cli/src/modules/build/lib/bundler/transforms.ts @@ -67,7 +67,7 @@ export const transforms = (options: TransformOptions): Transforms => { : 'builtin:swc-loader', options: { jsc: { - target: 'es2022', + target: 'es2023', externalHelpers: !isBackend, parser: { syntax: 'typescript', @@ -97,7 +97,7 @@ export const transforms = (options: TransformOptions): Transforms => { : 'builtin:swc-loader', options: { jsc: { - target: 'es2022', + target: 'es2023', externalHelpers: !isBackend, parser: { syntax: 'ecmascript', diff --git a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts index 971409cd93..90635f1a73 100644 --- a/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/modules/build/lib/packager/createDistWorkspace.ts @@ -25,7 +25,7 @@ import { tmpdir } from 'os'; import tar, { CreateOptions, FileOptions } from 'tar'; import partition from 'lodash/partition'; import { paths } from '../../../../lib/paths'; -import { run } from '../../../../lib/run'; +import { run } from '@backstage/cli-common'; import { dependencies as cliDependencies, devDependencies as cliDevDependencies, @@ -228,11 +228,11 @@ export async function createDistWorkspace( await runParallelWorkers({ items: customBuild, worker: async ({ name, dir, args }) => { - await run('yarn', ['run', 'build', ...(args || [])], { + await run(['yarn', 'run', 'build', ...(args || [])], { cwd: dir, - stdoutLogFunc: prefixLogFunc(`${name}: `, 'stdout'), - stderrLogFunc: prefixLogFunc(`${name}: `, 'stderr'), - }); + onStdout: prefixLogFunc(`${name}: `, 'stdout'), + onStderr: prefixLogFunc(`${name}: `, 'stderr'), + }).waitForExit(); }, }); } @@ -321,9 +321,9 @@ async function moveToDistWorkspace( console.log(`Repacking ${target.name} into dist workspace`); const archivePath = resolvePath(workspaceDir, archive); - await run('yarn', ['pack', '--filename', archivePath], { + await run(['yarn', 'pack', '--filename', archivePath], { cwd: target.dir, - }); + }).waitForExit(); const outputDir = relativePath(paths.targetRoot, target.dir); const absoluteOutputPath = resolvePath(workspaceDir, outputDir); diff --git a/packages/cli/src/modules/config/index.ts b/packages/cli/src/modules/config/index.ts index d62dbd4d26..3eabf5e9fc 100644 --- a/packages/cli/src/modules/config/index.ts +++ b/packages/cli/src/modules/config/index.ts @@ -62,7 +62,7 @@ export default createCliPlugin({ path: ['config:print'], description: 'Print the app configuration for the current package', execute: async ({ args, info }) => { - const argv = await yargs + const argv = await yargs() .options({ package: { type: 'string' }, lax: { type: 'boolean' }, @@ -82,7 +82,7 @@ export default createCliPlugin({ description: 'Validate that the given configuration loads and matches schema', execute: async ({ args }) => { - const argv = await yargs + const argv = await yargs() .options({ package: { type: 'string' }, lax: { type: 'boolean' }, @@ -105,7 +105,7 @@ export default createCliPlugin({ path: ['config:schema'], description: 'Print the JSON schema for the given configuration', execute: async ({ args }) => { - const argv = await yargs + const argv = await yargs() .options({ package: { type: 'string' }, format: { type: 'string' }, @@ -122,7 +122,7 @@ export default createCliPlugin({ path: ['config', 'schema'], description: 'Print the JSON schema for the given configuration', execute: async ({ args }) => { - const argv = await yargs + const argv = await yargs() .options({ package: { type: 'string' }, format: { type: 'string' }, diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 416c995541..97090d8b9a 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -16,14 +16,14 @@ import { version as cliVersion } from '../../../../package.json'; import os from 'os'; -import { runPlain } from '../../../lib/run'; +import { runOutput } from '@backstage/cli-common'; import { paths } from '../../../lib/paths'; import { Lockfile } from '../../../lib/versioning'; import fs from 'fs-extra'; export default async () => { await new Promise(async () => { - const yarnVersion = await runPlain('yarn --version'); + const yarnVersion = await runOutput(['yarn', '--version']); const isLocal = fs.existsSync(paths.resolveOwn('./src')); const backstageFile = paths.resolveTargetRoot('backstage.json'); diff --git a/packages/cli/src/modules/maintenance/commands/repo/clean.ts b/packages/cli/src/modules/maintenance/commands/repo/clean.ts index 51c31129ea..c76076ad55 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/clean.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/clean.ts @@ -14,14 +14,11 @@ * limitations under the License. */ -import { execFile as execFileCb } from 'child_process'; import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import { promisify } from 'util'; import { PackageGraph } from '@backstage/cli-node'; import { paths } from '../../../../lib/paths'; - -const execFile = promisify(execFileCb); +import { run } from '@backstage/cli-common'; export async function command(): Promise { const packages = await PackageGraph.listTargetPackages(); @@ -44,12 +41,9 @@ export async function command(): Promise { await fs.remove(resolvePath(pkg.dir, 'dist-types')); await fs.remove(resolvePath(pkg.dir, 'coverage')); } else if (cleanScript) { - const result = await execFile('yarn', ['run', 'clean'], { + await run(['yarn', 'run', 'clean'], { cwd: pkg.dir, - shell: true, - }); - process.stdout.write(result.stdout); - process.stderr.write(result.stderr); + }).waitForExit(); } } }), diff --git a/packages/cli/src/modules/migrate/commands/packageLintConfigs.ts b/packages/cli/src/modules/migrate/commands/packageLintConfigs.ts index c8e8ef3a42..8f5e395b98 100644 --- a/packages/cli/src/modules/migrate/commands/packageLintConfigs.ts +++ b/packages/cli/src/modules/migrate/commands/packageLintConfigs.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import { PackageGraph } from '@backstage/cli-node'; -import { runPlain } from '../../../lib/run'; +import { runOutput } from '@backstage/cli-common'; const PREFIX = `module.exports = require('@backstage/cli/config/eslint-factory')`; @@ -84,6 +84,6 @@ export async function command() { } if (hasPrettier) { - await runPlain('prettier', '--write', ...configPaths); + await runOutput(['prettier', '--write', ...configPaths]); } } diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts index 738610d09c..202704e030 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; import { Command } from 'commander'; -import * as runObj from '../../../../lib/run'; +import * as runObj from '@backstage/cli-common'; import bump, { bumpBackstageJsonVersion, createVersionFinder } from './bump'; import { registerMswTestHooks, withLogCollector } from '@backstage/test-utils'; import { YarnInfoInspectData } from '../../../../lib/versioning/packages'; @@ -60,21 +60,22 @@ jest.mock('ora', () => ({ })); let mockDir: MockDirectory; -jest.mock('@backstage/cli-common', () => ({ - ...jest.requireActual('@backstage/cli-common'), - findPaths: () => ({ - resolveTargetRoot(filename: string) { - return mockDir.resolve(filename); - }, - get targetDir() { - return mockDir.path; - }, - }), -})); - -jest.mock('../../../../lib/run', () => { +jest.mock('@backstage/cli-common', () => { + const actual = jest.requireActual('@backstage/cli-common'); return { - run: jest.fn(), + ...actual, + findPaths: () => ({ + resolveTargetRoot(filename: string) { + return mockDir.resolve(filename); + }, + get targetDir() { + return mockDir.path; + }, + }), + run: jest.fn().mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + }), }; }); @@ -184,7 +185,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -222,8 +226,7 @@ describe('bump', () => { expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -277,7 +280,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -318,8 +324,7 @@ describe('bump', () => { expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); expect(runObj.run).not.toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -373,7 +378,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -421,8 +429,7 @@ describe('bump', () => { expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -477,7 +484,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -526,14 +536,14 @@ describe('bump', () => { expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); expect(runObj.run).toHaveBeenCalledTimes(2); - expect(runObj.run).toHaveBeenCalledWith('yarn', [ + expect(runObj.run).toHaveBeenCalledWith([ + 'yarn', 'plugin', 'import', 'https://versions.backstage.io/v1/releases/0.0.1/yarn-plugin', ]); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -587,7 +597,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/releases/999.0.1/manifest.json', @@ -656,7 +669,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -763,7 +779,10 @@ describe('bump', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -813,8 +832,7 @@ describe('bump', () => { expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -873,7 +891,10 @@ describe('bump', () => { }); mockFetchPackageInfo.mockRejectedValue(new NotFoundError('Nope')); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://versions.backstage.io/v1/tags/main/manifest.json', @@ -1094,7 +1115,10 @@ describe('environment variables', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://custom.example.com/v1/tags/main/manifest.json', @@ -1131,8 +1155,7 @@ describe('environment variables', () => { expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -1186,7 +1209,10 @@ describe('environment variables', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main' } as unknown as Command); @@ -1215,8 +1241,7 @@ describe('environment variables', () => { expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); @@ -1253,7 +1278,10 @@ describe('environment variables', () => { }, }); - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + jest.spyOn(runObj, 'run').mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + } as any); worker.use( rest.get( 'https://custom.example.com/v1/tags/main/manifest.json', @@ -1291,14 +1319,14 @@ describe('environment variables', () => { ]); expect(runObj.run).toHaveBeenCalledTimes(2); - expect(runObj.run).toHaveBeenCalledWith('yarn', [ + expect(runObj.run).toHaveBeenCalledWith([ + 'yarn', 'plugin', 'import', 'https://custom.example.com/v1/releases/1.5.0/yarn-plugin', ]); expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + ['yarn', 'install'], expect.any(Object), ); }); diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.ts b/packages/cli/src/modules/migrate/commands/versions/bump.ts index 0d5e2f2f35..cad477188b 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { BACKSTAGE_JSON, bootstrapEnvProxyAgents } from '@backstage/cli-common'; -maybeBootstrapProxy(); +bootstrapEnvProxyAgents(); import { env } from 'process'; import fs from 'fs-extra'; @@ -32,7 +33,6 @@ import { mapDependencies, YarnInfoInspectData, } from '../../../../lib/versioning'; -import { BACKSTAGE_JSON } from '@backstage/cli-common'; import { runParallelWorkers } from '../../../../lib/parallel'; import { getManifestByReleaseLine, @@ -41,27 +41,7 @@ import { } from '@backstage/release-manifests'; import { migrateMovedPackages } from './migrate'; import { runYarnInstall } from '../../lib/utils'; -import { run } from '../../../../lib/run'; - -function maybeBootstrapProxy() { - // see https://www.npmjs.com/package/global-agent - const globalAgentNamespace = - process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE ?? 'GLOBAL_AGENT_'; - if ( - process.env[`${globalAgentNamespace}HTTP_PROXY`] || - process.env[`${globalAgentNamespace}HTTPS_PROXY`] - ) { - const globalAgent = - require('global-agent') as typeof import('global-agent'); - globalAgent.bootstrap(); - } - - if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) { - const { setGlobalDispatcher, EnvHttpProxyAgent } = - require('undici') as typeof import('undici'); - setGlobalDispatcher(new EnvHttpProxyAgent()); - } -} +import { run } from '@backstage/cli-common'; const DEP_TYPES = [ 'dependencies', @@ -155,7 +135,7 @@ export default async (opts: OptionValues) => { ? `${env.BACKSTAGE_VERSIONS_BASE_URL}/v1/releases/${releaseManifest.releaseVersion}/yarn-plugin` : `https://versions.backstage.io/v1/releases/${releaseManifest.releaseVersion}/yarn-plugin`; - await run('yarn', ['plugin', 'import', yarnPluginUrl]); + await run(['yarn', 'plugin', 'import', yarnPluginUrl]).waitForExit(); console.log(); } diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts index 9ff00fd285..936d10e24c 100644 --- a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts @@ -17,7 +17,7 @@ import { MockDirectory, createMockDirectory, } from '@backstage/backend-test-utils'; -import * as run from '../../../../lib/run'; +import * as runObj from '@backstage/cli-common'; import migrate from './migrate'; import { withLogCollector } from '@backstage/test-utils'; import fs from 'fs-extra'; @@ -33,21 +33,22 @@ jest.mock('chalk', () => ({ })); let mockDir: MockDirectory; -jest.mock('@backstage/cli-common', () => ({ - ...jest.requireActual('@backstage/cli-common'), - findPaths: () => ({ - resolveTargetRoot(filename: string) { - return mockDir.resolve(filename); - }, - get targetDir() { - return mockDir.path; - }, - }), -})); - -jest.mock('../../../../lib/run', () => { +jest.mock('@backstage/cli-common', () => { + const actual = jest.requireActual('@backstage/cli-common'); return { - run: jest.fn(), + ...actual, + findPaths: () => ({ + resolveTargetRoot(filename: string) { + return mockDir.resolve(filename); + }, + get targetDir() { + return mockDir.path; + }, + }), + run: jest.fn().mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + }), }; }); @@ -58,8 +59,15 @@ function expectLogsToMatch(receivedLogs: String[], expected: String[]): void { describe('versions:migrate', () => { mockDir = createMockDirectory(); + beforeEach(() => { + (runObj.run as jest.Mock).mockReturnValue({ + exitCode: null, + waitForExit: jest.fn().mockResolvedValue(undefined), + }); + }); + afterEach(() => { - jest.resetAllMocks(); + (runObj.run as jest.Mock).mockClear(); }); it('should bump to the moved version when the package is moved', async () => { @@ -116,8 +124,6 @@ describe('versions:migrate', () => { }, }); - jest.spyOn(run, 'run').mockResolvedValue(undefined); - const { warn, log: logs } = await withLogCollector(async () => { await migrate({}); }); @@ -136,10 +142,9 @@ describe('versions:migrate', () => { 'Could not find package.json for @backstage/theme@^1.0.0 in b (dependencies)', ]); - expect(run.run).toHaveBeenCalledTimes(1); - expect(run.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + expect(runObj.run).toHaveBeenCalledTimes(1); + expect(runObj.run).toHaveBeenCalledWith( + ['yarn', 'install'], expect.any(Object), ); @@ -227,16 +232,13 @@ describe('versions:migrate', () => { }, }); - jest.spyOn(run, 'run').mockResolvedValue(undefined); - await withLogCollector(async () => { await migrate({}); }); - expect(run.run).toHaveBeenCalledTimes(1); - expect(run.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + expect(runObj.run).toHaveBeenCalledTimes(1); + expect(runObj.run).toHaveBeenCalledWith( + ['yarn', 'install'], expect.any(Object), ); @@ -259,7 +261,7 @@ describe('versions:migrate', () => { ); }); - it('should replaces the occurrences of changed packages, and is careful', async () => { + it('should replace occurrences of changed packages, and is careful', async () => { mockDir.setContent({ 'package.json': JSON.stringify({ workspaces: { @@ -314,16 +316,13 @@ describe('versions:migrate', () => { }, }); - jest.spyOn(run, 'run').mockResolvedValue(undefined); - await withLogCollector(async () => { await migrate({}); }); - expect(run.run).toHaveBeenCalledTimes(1); - expect(run.run).toHaveBeenCalledWith( - 'yarn', - ['install'], + expect(runObj.run).toHaveBeenCalledTimes(1); + expect(runObj.run).toHaveBeenCalledWith( + ['yarn', 'install'], expect.any(Object), ); diff --git a/packages/cli/src/modules/migrate/lib/utils.ts b/packages/cli/src/modules/migrate/lib/utils.ts index 5b7b544c87..c85f56dabb 100644 --- a/packages/cli/src/modules/migrate/lib/utils.ts +++ b/packages/cli/src/modules/migrate/lib/utils.ts @@ -16,7 +16,7 @@ import ora from 'ora'; import chalk from 'chalk'; -import { run } from '../../../lib/run'; +import { run } from '@backstage/cli-common'; export async function runYarnInstall() { const spinner = ora({ @@ -27,7 +27,7 @@ export async function runYarnInstall() { const installOutput = new Array(); try { - await run('yarn', ['install'], { + await run(['yarn', 'install'], { env: { FORCE_COLOR: 'true', // We filter out all of the npm_* environment variables that are added when @@ -39,9 +39,9 @@ export async function runYarnInstall() { ), ), }, - stdoutLogFunc: data => installOutput.push(data), - stderrLogFunc: data => installOutput.push(data), - }); + onStdout: data => installOutput.push(data), + onStderr: data => installOutput.push(data), + }).waitForExit(); spinner.succeed(); } catch (error) { spinner.fail(); diff --git a/packages/cli/src/modules/new/commands/new.test.ts b/packages/cli/src/modules/new/commands/new.test.ts index 77ee96cd09..2183d80062 100644 --- a/packages/cli/src/modules/new/commands/new.test.ts +++ b/packages/cli/src/modules/new/commands/new.test.ts @@ -23,6 +23,8 @@ describe.each([ [undefined, undefined, undefined], ['internal', '@internal/', 'backstage-plugin-'], ['internal/', '@internal/', 'backstage-plugin-'], + ['@internal', '@internal/', 'backstage-plugin-'], + ['@internal/', '@internal/', 'backstage-plugin-'], ['acme-backstage', '@acme-backstage/', 'plugin-'], ['acme-backstage/', '@acme-backstage/', 'plugin-'], ['acme-backstage-plugins', '@acme-backstage-plugins/', 'plugin-'], diff --git a/packages/cli/src/modules/new/commands/new.ts b/packages/cli/src/modules/new/commands/new.ts index 9d09f42cf3..a38316dd62 100644 --- a/packages/cli/src/modules/new/commands/new.ts +++ b/packages/cli/src/modules/new/commands/new.ts @@ -42,7 +42,10 @@ export default async (opts: ArgOptions) => { let pluginInfix: string | undefined = undefined; let packagePrefix: string | undefined = undefined; if (scope) { - packagePrefix = scope.includes('/') ? `@${scope}` : `@${scope}/`; + const normalizedScope = scope.startsWith('@') ? scope : `@${scope}`; + packagePrefix = normalizedScope.includes('/') + ? normalizedScope + : `${normalizedScope}/`; pluginInfix = scope.includes('backstage') ? 'plugin-' : 'backstage-plugin-'; } diff --git a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts index 6d34e35e4c..8e31f740cd 100644 --- a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts @@ -24,6 +24,7 @@ import { } from '../types'; import { installNewPackage } from './installNewPackage'; import { writeTemplateContents } from './writeTemplateContents'; +import { run } from '@backstage/cli-common'; type ExecuteNewTemplateOptions = { config: PortableTemplateConfig; @@ -54,14 +55,25 @@ export async function executePortableTemplate( } if (!options.skipInstall) { - await Task.forCommand('yarn install', { - cwd: targetDir, - optional: true, - }); - await Task.forCommand('yarn lint --fix', { - cwd: targetDir, - optional: true, - }); + for (const command of [ + ['yarn', 'install'], + ['yarn', 'lint', '--fix'], + ]) { + const commandStr = command.join(' '); + try { + await Task.forItem('executing', commandStr, async () => { + await run(command, { + cwd: targetDir, + stdio: 'ignore', + }).waitForExit(); + }); + } catch (error) { + assertError(error); + Task.error( + `Warning: Failed to execute command '${commandStr}', ${error}`, + ); + } + } } Task.log(); diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.test.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.test.ts index b8a7940bcf..f8167ef339 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.test.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.test.ts @@ -196,7 +196,7 @@ describe('loadPortableTemplateConfig', () => { private: true, version: '0.1.0', packageNamePrefix: '@internal/', - packageNamePluginInfix: 'plugin-', + packageNamePluginInfix: 'backstage-plugin-', }); }); @@ -316,7 +316,7 @@ describe('loadPortableTemplateConfig', () => { version: '0.1.0', private: true, packageNamePrefix: '@internal/', - packageNamePluginInfix: 'plugin-', + packageNamePluginInfix: 'backstage-plugin-', }); await expect( @@ -333,7 +333,7 @@ describe('loadPortableTemplateConfig', () => { version: '0.1.0', private: true, packageNamePrefix: '@internal/', - packageNamePluginInfix: 'plugin-', + packageNamePluginInfix: 'backstage-plugin-', }); }); }); diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts index 5ef61bfb6b..2e736d9c29 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts @@ -70,6 +70,21 @@ type LoadConfigOptions = { overrides?: Partial; }; +function computePackageNamePluginInfix( + packageNamePrefix: string, + namePluginInfix?: string, +) { + const packageNamePluginInfix = + namePluginInfix ?? + (packageNamePrefix.includes('backstage') + ? defaults.packageNamePluginInfix + : 'backstage-plugin-'); + + return { + packageNamePluginInfix, + }; +} + export async function loadPortableTemplateConfig( options: LoadConfigOptions = {}, ): Promise { @@ -116,6 +131,16 @@ export async function loadPortableTemplateConfig( templateNameConflicts.set(pointer.name, rawPointer); } + const packageNamePrefix = + overrides.packageNamePrefix ?? + config?.globals?.namePrefix ?? + defaults.packageNamePrefix; + + const { packageNamePluginInfix } = computePackageNamePluginInfix( + packageNamePrefix, + overrides.packageNamePluginInfix ?? config?.globals?.namePluginInfix, + ); + return { isUsingDefaultTemplates: !config?.templates, templatePointers: templatePointerEntries.map(({ pointer }) => pointer), @@ -126,14 +151,8 @@ export async function loadPortableTemplateConfig( overrides.publishRegistry ?? config?.globals?.publishRegistry ?? defaults.publishRegistry, - packageNamePrefix: - overrides.packageNamePrefix ?? - config?.globals?.namePrefix ?? - defaults.packageNamePrefix, - packageNamePluginInfix: - overrides.packageNamePluginInfix ?? - config?.globals?.namePluginInfix ?? - defaults.packageNamePluginInfix, + packageNamePrefix, + packageNamePluginInfix, }; } diff --git a/packages/cli/src/modules/new/lib/tasks.ts b/packages/cli/src/modules/new/lib/tasks.ts index 2922f34f35..f3620d242f 100644 --- a/packages/cli/src/modules/new/lib/tasks.ts +++ b/packages/cli/src/modules/new/lib/tasks.ts @@ -16,11 +16,6 @@ import chalk from 'chalk'; import ora from 'ora'; -import { promisify } from 'util'; -import { exec as execCb } from 'child_process'; -import { assertError } from '@backstage/errors'; - -const exec = promisify(execCb); const TASK_NAME_MAX_LENGTH = 14; @@ -64,30 +59,4 @@ export class Task { throw error; } } - - static async forCommand( - command: string, - options?: { cwd?: string; optional?: boolean }, - ) { - try { - await Task.forItem('executing', command, async () => { - await exec(command, { cwd: options?.cwd }); - }); - } catch (error) { - assertError(error); - if (error.stderr) { - process.stderr.write(error.stderr as Buffer); - } - if (error.stdout) { - process.stdout.write(error.stdout as Buffer); - } - if (options?.optional) { - Task.error(`Warning: Failed to execute command ${chalk.cyan(command)}`); - } else { - throw new Error( - `Failed to execute command '${chalk.cyan(command)}', ${error}`, - ); - } - } - } } diff --git a/packages/cli/src/modules/test/commands/package/test.ts b/packages/cli/src/modules/test/commands/package/test.ts index e7ef0af302..1ecf5b395e 100644 --- a/packages/cli/src/modules/test/commands/package/test.ts +++ b/packages/cli/src/modules/test/commands/package/test.ts @@ -16,7 +16,7 @@ import { Command, OptionValues } from 'commander'; import { paths } from '../../../../lib/paths'; -import { runCheck } from '../../../../lib/run'; +import { runCheck } from '@backstage/cli-common'; function includesAnyOf(hayStack: string[], ...needles: string[]) { for (const needle of needles) { @@ -55,8 +55,8 @@ export default async (_opts: OptionValues, cmd: Command) => { !includesAnyOf(args, '--watch', '--watchAll') ) { const isGitRepo = () => - runCheck('git', 'rev-parse', '--is-inside-work-tree'); - const isMercurialRepo = () => runCheck('hg', '--cwd', '.', 'root'); + runCheck(['git', 'rev-parse', '--is-inside-work-tree']); + const isMercurialRepo = () => runCheck(['hg', '--cwd', '.', 'root']); if ((await isGitRepo()) || (await isMercurialRepo())) { args.push('--watch'); diff --git a/packages/cli/src/modules/test/commands/repo/test.ts b/packages/cli/src/modules/test/commands/repo/test.ts index f0520d29e2..fd18ba09fe 100644 --- a/packages/cli/src/modules/test/commands/repo/test.ts +++ b/packages/cli/src/modules/test/commands/repo/test.ts @@ -22,7 +22,7 @@ import { relative as relativePath } from 'path'; import { Command, OptionValues } from 'commander'; import { Lockfile, PackageGraph } from '@backstage/cli-node'; import { paths } from '../../../../lib/paths'; -import { runCheck, runPlain } from '../../../../lib/run'; +import { runCheck, runOutput } from '@backstage/cli-common'; import { isChildPath } from '@backstage/cli-common'; import { SuccessCache } from '../../../../lib/cache/SuccessCache'; @@ -63,14 +63,14 @@ async function readPackageTreeHashes(graph: PackageGraph) { ...pkg, path: relativePath(paths.targetRoot, pkg.dir), })); - const output = await runPlain( + const output = await runOutput([ 'git', 'ls-tree', - '--format="%(objectname)=%(path)"', + '--format=%(objectname)=%(path)', 'HEAD', '--', ...pkgs.map(pkg => pkg.path), - ); + ]); const map = new Map( output @@ -175,8 +175,8 @@ export async function command(opts: OptionValues, cmd: Command): Promise { !hasFlags('--coverage', '--watch', '--watchAll') ) { const isGitRepo = () => - runCheck('git', 'rev-parse', '--is-inside-work-tree'); - const isMercurialRepo = () => runCheck('hg', '--cwd', '.', 'root'); + runCheck(['git', 'rev-parse', '--is-inside-work-tree']); + const isMercurialRepo = () => runCheck(['hg', '--cwd', '.', 'root']); if ((await isGitRepo()) || (await isMercurialRepo())) { isSingleWatchMode = true; diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 7fb76bd741..0f51242206 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -36,18 +36,18 @@ const exportValues = { }; const expectedExports = { - commonJs: { + commonJs: expect.objectContaining({ ...exportValues.commonJs, dyn: exportValues.all, default: { ...exportValues.commonJs, dyn: exportValues.all, }, - }, - module: { + }), + module: expect.objectContaining({ ...exportValues.all, dyn: exportValues.all, - }, + }), }; function loadFixture(fixture: string) { diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 89c2f0ecfa..13e6bfe7f6 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/codemods +## 0.1.53-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 0.1.53-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + ## 0.1.52 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 92b3dc7042..f78579f714 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/codemods", - "version": "0.1.52", + "version": "0.1.53-next.1", "description": "A collection of codemods for Backstage projects", "backstage": { "role": "cli" @@ -52,6 +52,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/jscodeshift": "^0.12.0", - "@types/node": "^20.16.0" + "@types/node": "^22.13.14" } } diff --git a/packages/codemods/src/action.ts b/packages/codemods/src/action.ts index b955ab17ea..467dd35f82 100644 --- a/packages/codemods/src/action.ts +++ b/packages/codemods/src/action.ts @@ -15,11 +15,9 @@ */ import { relative as relativePath } from 'path'; -import { spawn } from 'child_process'; import { OptionValues } from 'commander'; -import { findPaths } from '@backstage/cli-common'; +import { findPaths, run } from '@backstage/cli-common'; import { platform } from 'os'; -import { ExitCodeError } from './errors'; // eslint-disable-next-line no-restricted-syntax const paths = findPaths(__dirname); @@ -51,42 +49,25 @@ export function createCodemodAction(name: string) { console.log(`Running jscodeshift with these arguments: ${args.join(' ')}`); - let command; + let commandArgs: string[]; if (platform() === 'win32') { - command = 'jscodeshift'; + commandArgs = ['jscodeshift', ...args]; } else { // jscodeshift ships a slightly broken bin script with windows // line endings so we need to execute it using node rather than // letting the `#!/usr/bin/env node` take care of it - command = process.argv0; - args.unshift(require.resolve('.bin/jscodeshift')); + commandArgs = [ + process.argv0, + require.resolve('.bin/jscodeshift'), + ...args, + ]; } - const child = spawn(command, args, { - stdio: 'inherit', - shell: true, + await run(commandArgs, { env: { ...process.env, FORCE_COLOR: 'true', }, - }); - - if (typeof child.exitCode === 'number') { - if (child.exitCode) { - throw new ExitCodeError(child.exitCode, name); - } - return; - } - - await new Promise((resolve, reject) => { - child.once('error', error => reject(error)); - child.once('exit', code => { - if (code) { - reject(new ExitCodeError(code, name)); - } else { - resolve(); - } - }); - }); + }).waitForExit(); }; } diff --git a/packages/codemods/src/errors.ts b/packages/codemods/src/errors.ts index 2f67b94ae1..e27f66895c 100644 --- a/packages/codemods/src/errors.ts +++ b/packages/codemods/src/errors.ts @@ -15,6 +15,7 @@ */ import chalk from 'chalk'; +import { ExitCodeError } from '@backstage/cli-common'; export class CustomError extends Error { get name(): string { @@ -22,19 +23,6 @@ export class CustomError extends Error { } } -export class ExitCodeError extends CustomError { - readonly code: number; - - constructor(code: number, command?: string) { - if (command) { - super(`Command '${command}' exited with code ${code}`); - } else { - super(`Child exited with code ${code}`); - } - this.code = code; - } -} - export function exitWithError(error: Error): never { if (error instanceof ExitCodeError) { process.stderr.write(`\n${chalk.red(error.message)}\n\n`); diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index c2433f3c35..00980c43bd 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/config-loader +## 1.10.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 1.10.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 1.10.6-next.0 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 10afca8ba4..24b71ce824 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.10.6-next.0", + "version": "1.10.7-next.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" @@ -49,7 +49,7 @@ "json-schema-traverse": "^1.0.0", "lodash": "^4.17.21", "minimist": "^1.2.5", - "typescript-json-schema": "^0.65.0", + "typescript-json-schema": "^0.67.0", "yaml": "^2.0.0" }, "devDependencies": { diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 596a67a1ed..6bf73fb546 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/config +## 1.3.6 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. + ## 1.3.6-next.0 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 75d15678c0..47e1e0a9e7 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config", - "version": "1.3.6-next.0", + "version": "1.3.6", "description": "Config API used by Backstage core, backend, and CLI", "backstage": { "role": "common-library" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index dbab1f871d..cc78499c94 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/core-app-api +## 1.19.3-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.2 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## 1.19.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.1 + ## 1.19.2-next.0 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 7cd3712cc0..d0c2d2b512 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.2-next.0", + "version": "1.19.3-next.0", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 0cecc22f4a..e039394946 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -20,6 +20,7 @@ import { AuthProviderInfo } from '@backstage/core-plugin-api'; import { AuthRequestOptions } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; +import { BackstageIdentityResponse as BackstageIdentityResponse_2 } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; @@ -55,6 +56,7 @@ import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { openshiftAuthApiRef } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; +import { ProfileInfo as ProfileInfo_2 } from '@backstage/frontend-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import PropTypes from 'prop-types'; @@ -62,6 +64,7 @@ import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; import { SessionState } from '@backstage/core-plugin-api'; +import { SessionState as SessionState_2 } from '@backstage/frontend-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; @@ -511,13 +514,13 @@ export class MicrosoftAuth { // (undocumented) getBackstageIdentity( options?: AuthRequestOptions, - ): Promise; + ): Promise; // (undocumented) getIdToken(options?: AuthRequestOptions): Promise; // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; + getProfile(options?: AuthRequestOptions): Promise; // (undocumented) - sessionState$(): Observable; + sessionState$(): Observable; // (undocumented) signIn(): Promise; // (undocumented) diff --git a/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts index 4e5397f65d..138d00c09c 100644 --- a/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts +++ b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts @@ -17,20 +17,35 @@ import { AlertApi, AlertMessage } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; import { PublishSubject } from '../../../lib/subjects'; +import ObservableImpl from 'zen-observable'; /** * Base implementation for the AlertApi that simply forwards alerts to consumers. * + * Recent alerts are buffered and replayed to new subscribers to prevent + * missing alerts that were posted before subscription. + * * @public */ export class AlertApiForwarder implements AlertApi { private readonly subject = new PublishSubject(); + private readonly recentAlerts: AlertMessage[] = []; + private readonly maxBufferSize = 10; post(alert: AlertMessage) { + this.recentAlerts.push(alert); + if (this.recentAlerts.length > this.maxBufferSize) { + this.recentAlerts.shift(); + } this.subject.next(alert); } alert$(): Observable { - return this.subject; + return new ObservableImpl(subscriber => { + for (const alert of this.recentAlerts) { + subscriber.next(alert); + } + return this.subject.subscribe(subscriber); + }); } } diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts index de21990cb3..2d4bb82668 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -37,12 +37,12 @@ import ObservableImpl from 'zen-observable'; import { toInternalTranslationResource, InternalTranslationResourceLoader, -} from '../../../../../core-plugin-api/src/translation/TranslationResource'; +} from '../../../../../frontend-plugin-api/src/translation/TranslationResource'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalTranslationRef, InternalTranslationRef, -} from '../../../../../core-plugin-api/src/translation/TranslationRef'; +} from '../../../../../frontend-plugin-api/src/translation/TranslationRef'; import { Observable } from '@backstage/types'; import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector'; import { createElement, Fragment, ReactNode, isValidElement } from 'react'; diff --git a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts index 5f56793cae..1824937a02 100644 --- a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts @@ -31,11 +31,11 @@ export type ApiFactoryScope = | 'app' // Factories registered in the app, overriding default ones | 'static'; // APIs that can't be overridden, e.g. config -enum ScopePriority { - default = 10, - app = 50, - static = 100, -} +const ScopePriority = { + default: 10, + app: 50, + static: 100, +} as const; type FactoryTuple = { priority: number; diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index b8d03b5e1c..4f3d30deda 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/core-compat-api +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.5.4 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-plugin-api@1.12.0 + ## 0.5.4-next.0 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index cedf7e661e..7bb92d26fc 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.4-next.0", + "version": "0.5.5-next.0", "backstage": { "role": "web-library" }, @@ -34,8 +34,10 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.test.ts b/packages/core-compat-api/src/convertLegacyRouteRef.test.ts index f09060212c..c21ac8c5bc 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.test.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.test.ts @@ -31,13 +31,11 @@ import { createExternalRouteRef as createNewExternalRouteRef, } from '@backstage/frontend-plugin-api'; import { convertLegacyRouteRef } from './convertLegacyRouteRef'; - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalRouteRef as toInternalNewRouteRef } from '../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalSubRouteRef as toInternalNewSubRouteRef } from '../../frontend-plugin-api/src/routing/SubRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef as toInternalNewExternalRouteRef } from '../../frontend-plugin-api/src/routing/ExternalRouteRef'; +import { + OpaqueExternalRouteRef, + OpaqueRouteRef, + OpaqueSubRouteRef, +} from '@internal/frontend'; describe('convertLegacyRouteRef', () => { it('converts old to new', () => { @@ -85,48 +83,40 @@ describe('convertLegacyRouteRef', () => { expect(ref3).toBe(ref3Converted); expect(ref4).toBe(ref4Converted); - const ref1Internal = toInternalNewRouteRef(ref1Converted); - const ref2Internal = toInternalNewRouteRef(ref2Converted); - const ref1sub1Internal = toInternalNewSubRouteRef(ref1sub1Converted); - const ref1sub2Internal = toInternalNewSubRouteRef(ref1sub2Converted); - const ref2sub1Internal = toInternalNewSubRouteRef(ref2sub1Converted); - const ref3Internal = toInternalNewExternalRouteRef(ref3Converted); - const ref4Internal = toInternalNewExternalRouteRef(ref4Converted); + const ref1Internal = OpaqueRouteRef.toInternal(ref1Converted); + const ref2Internal = OpaqueRouteRef.toInternal(ref2Converted); + const ref1sub1Internal = OpaqueSubRouteRef.toInternal(ref1sub1Converted); + const ref1sub2Internal = OpaqueSubRouteRef.toInternal(ref1sub2Converted); + const ref2sub1Internal = OpaqueSubRouteRef.toInternal(ref2sub1Converted); + const ref3Internal = OpaqueExternalRouteRef.toInternal(ref3Converted); + const ref4Internal = OpaqueExternalRouteRef.toInternal(ref4Converted); - expect(ref1Internal.getDescription()).toBe( - 'routeRef{type=absolute,id=ref1}', - ); + expect(ref1Internal.getDescription()).toBe('ref1'); expect(ref1Internal.getParams()).toEqual([]); - expect(ref2Internal.getDescription()).toBe( - 'routeRef{type=absolute,id=ref2}', - ); + expect(ref2Internal.getDescription()).toBe('ref2'); expect(ref2Internal.getParams()).toEqual(['p1', 'p2']); expect(ref1sub1Internal.getDescription()).toBe( - 'routeRef{type=sub,id=sub1}', + 'at /sub1 with parent routeRef{type=absolute,id=ref1}', ); expect(ref1sub1Internal.getParams()).toEqual([]); expect(ref1sub1Internal.getParent()).toBe(ref1); expect(ref1sub2Internal.getDescription()).toBe( - 'routeRef{type=sub,id=sub2}', + 'at /sub2/:p3 with parent routeRef{type=absolute,id=ref1}', ); expect(ref1sub2Internal.getParams()).toEqual(['p3']); expect(ref1sub2Internal.getParent()).toBe(ref1); expect(ref2sub1Internal.getDescription()).toBe( - 'routeRef{type=sub,id=sub1}', + 'at /sub1/:p3 with parent routeRef{type=absolute,id=ref2}', ); expect(ref2sub1Internal.getParams()).toEqual(['p1', 'p2', 'p3']); expect(ref2sub1Internal.getParent()).toBe(ref2); expect(ref3Internal.getDefaultTarget()).toBe(undefined); - expect(ref3Internal.getDescription()).toBe( - 'routeRef{type=external,id=ref3}', - ); + expect(ref3Internal.getDescription()).toBe('ref3'); expect(ref3Internal.getParams()).toEqual([]); expect(ref4Internal.getDefaultTarget()).toBe('ref2'); - expect(ref4Internal.getDescription()).toBe( - 'routeRef{type=external,id=ref4}', - ); + expect(ref4Internal.getDescription()).toBe('ref4'); expect(ref4Internal.getParams()).toEqual(['p1', 'p2']); }); @@ -168,34 +158,34 @@ describe('convertLegacyRouteRef', () => { expect(ref3).toBe(ref3Converted); expect(ref4).toBe(ref4Converted); - expect(String(ref1Converted)).toMatch(/^RouteRef\{created at '.*'\}$/); + expect(String(ref1Converted)).toMatch(/^routeRef\{id=undefined,at='.*'\}$/); expect(ref1Converted.params).toEqual([]); - expect(String(ref2Converted)).toMatch(/^RouteRef\{created at '.*'\}$/); + expect(String(ref2Converted)).toMatch(/^routeRef\{id=undefined,at='.*'\}$/); expect(ref2Converted.params).toEqual(['p1', 'p2']); expect(String(ref1sub1Converted)).toMatch( - /^SubRouteRef\{at \/sub1 with parent created at '.*'\}$/, + /^subRouteRef\{path='\/sub1',parent=routeRef\{id=undefined,at='.*'\}\}$/, ); expect(ref1sub1Converted.params).toEqual([]); expect(ref1sub1Converted.parent).toBe(ref1); expect(String(ref1sub2Converted)).toMatch( - /^SubRouteRef\{at \/sub2\/:p3 with parent created at '.*'\}$/, + /^subRouteRef\{path='\/sub2\/:p3',parent=routeRef\{id=undefined,at='.*'\}\}$/, ); expect(ref1sub2Converted.params).toEqual(['p3']); expect(ref1sub2Converted.parent).toBe(ref1); expect(String(ref2sub1Converted)).toMatch( - /^SubRouteRef\{at \/sub1\/:p3 with parent created at '.*'\}$/, + /^subRouteRef\{path='\/sub1\/:p3',parent=routeRef\{id=undefined,at='.*'\}\}$/, ); expect(ref2sub1Converted.params).toEqual(['p1', 'p2', 'p3']); expect(ref2sub1Converted.parent).toBe(ref2); expect(String(ref3Converted)).toMatch( - /^ExternalRouteRef\{created at '.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*'\}$/, ); expect(ref3Converted.params).toEqual([]); expect(ref3Converted.optional).toBe(true); expect(String(ref4Converted)).toMatch( - /^ExternalRouteRef\{created at '.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*'\}$/, ); expect(ref4Converted.params).toEqual(['p1', 'p2']); expect(ref4Converted.optional).toBe(true); diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts index 40a95a81ff..e490d83a19 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts @@ -32,13 +32,11 @@ import { createSubRouteRef, createExternalRouteRef, } from '@backstage/frontend-plugin-api'; - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalRouteRef } from '../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalSubRouteRef } from '../../frontend-plugin-api/src/routing/SubRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef } from '../../frontend-plugin-api/src/routing/ExternalRouteRef'; +import { + OpaqueRouteRef, + OpaqueSubRouteRef, + OpaqueExternalRouteRef, +} from '@internal/frontend'; /** * Converts a legacy route ref type to the new system. @@ -184,29 +182,29 @@ function convertNewToOld( ref: RouteRef | SubRouteRef | ExternalRouteRef, ): LegacyRouteRef | LegacySubRouteRef | LegacyExternalRouteRef { if (ref.$$type === '@backstage/RouteRef') { - const newRef = toInternalRouteRef(ref); + const newRef = OpaqueRouteRef.toInternal(ref); return Object.assign(ref, { [routeRefType]: 'absolute', params: newRef.getParams(), title: newRef.getDescription(), - } as Omit) as unknown as LegacyRouteRef; + } as Omit) as unknown as LegacyRouteRef; } if (ref.$$type === '@backstage/SubRouteRef') { - const newRef = toInternalSubRouteRef(ref); + const newRef = OpaqueSubRouteRef.toInternal(ref); return Object.assign(ref, { [routeRefType]: 'sub', parent: convertLegacyRouteRef(newRef.getParent()), params: newRef.getParams(), - } as Omit) as unknown as LegacySubRouteRef; + } as Omit) as unknown as LegacySubRouteRef; } if (ref.$$type === '@backstage/ExternalRouteRef') { - const newRef = toInternalExternalRouteRef(ref); + const newRef = OpaqueExternalRouteRef.toInternal(ref); return Object.assign(ref, { [routeRefType]: 'external', optional: true, params: newRef.getParams(), defaultTarget: newRef.getDefaultTarget(), - } as Omit) as unknown as LegacyExternalRouteRef; + } as Omit) as unknown as LegacyExternalRouteRef; } throw new Error( @@ -221,7 +219,7 @@ function convertOldToNew( if (type === 'absolute') { const legacyRef = ref as LegacyRouteRef; const legacyRefStr = String(legacyRef); - const newRef = toInternalRouteRef( + const newRef = OpaqueRouteRef.toInternal( createRouteRef<{ [key in string]: string }>({ params: legacyRef.params as string[], }), @@ -247,7 +245,7 @@ function convertOldToNew( if (type === 'sub') { const legacyRef = ref as LegacySubRouteRef; const legacyRefStr = String(legacyRef); - const newRef = toInternalSubRouteRef( + const newRef = OpaqueSubRouteRef.toInternal( createSubRouteRef({ path: legacyRef.path, parent: convertLegacyRouteRef(legacyRef.parent), @@ -274,7 +272,7 @@ function convertOldToNew( if (type === 'external') { const legacyRef = ref as LegacyExternalRouteRef; const legacyRefStr = String(legacyRef); - const newRef = toInternalExternalRouteRef( + const newRef = OpaqueExternalRouteRef.toInternal( createExternalRouteRef<{ [key in string]: string }>({ params: legacyRef.params as string[], defaultTarget: diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 63a2846889..ea2023e845 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/core-components +## 0.18.4-next.1 + +### 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 +- 5d52dab: Add i18n support for LogViewer search control + +## 0.18.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## 0.18.3 + +### Patch Changes + +- 1c7e1ce: - Revert `viewbox` back to old values. + - Added `fullScreen` prop to `EntityCatalogGraphCard` +- 96ad674: Line numbers in LogViewer will not be selectable in UI anymore +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + +## 0.18.3-next.2 + +### Patch Changes + +- 96ad674: Line numbers in LogViewer will not be selectable in UI anymore + +## 0.18.3-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- Updated dependencies + - @backstage/core-plugin-api@1.11.2-next.1 + ## 0.18.3-next.0 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index cbd3450886..2c3cee0c46 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.18.3-next.0", + "version": "0.18.4-next.1", "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index 96ddebcd3a..785b97ce28 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) export const coreComponentsTranslationRef: TranslationRef< @@ -63,6 +63,7 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'autoLogout.stillTherePrompt.buttonText': "Yes! Don't log me out"; readonly 'dependencyGraph.fullscreenTooltip': 'Toggle fullscreen'; readonly 'proxiedSignInPage.title': 'You do not appear to be signed in. Please try reloading the browser page.'; + readonly 'logViewer.searchField.placeholder': 'Search'; } >; diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index 9c9992deff..02fbb04805 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -283,11 +283,18 @@ export interface DependencyGraphProps // @public export namespace DependencyGraphTypes { - export enum Alignment { - DOWN_LEFT = 'DL', - DOWN_RIGHT = 'DR', - UP_LEFT = 'UL', - UP_RIGHT = 'UR', + // (undocumented) + export type Alignment = (typeof Alignment)[keyof typeof Alignment]; + // (undocumented) + export namespace Alignment { + // (undocumented) + export type DOWN_LEFT = typeof Alignment.DOWN_LEFT; + // (undocumented) + export type DOWN_RIGHT = typeof Alignment.DOWN_RIGHT; + // (undocumented) + export type UP_LEFT = typeof Alignment.UP_LEFT; + // (undocumented) + export type UP_RIGHT = typeof Alignment.UP_RIGHT; } export type DependencyEdge = T & { from: string; @@ -298,25 +305,54 @@ export namespace DependencyGraphTypes { export type DependencyNode = T & { id: string; }; - export enum Direction { - BOTTOM_TOP = 'BT', - LEFT_RIGHT = 'LR', - RIGHT_LEFT = 'RL', - TOP_BOTTOM = 'TB', - } - export enum LabelPosition { + // (undocumented) + export type Direction = (typeof Direction)[keyof typeof Direction]; + // (undocumented) + export namespace Direction { // (undocumented) - CENTER = 'c', + export type BOTTOM_TOP = typeof Direction.BOTTOM_TOP; // (undocumented) - LEFT = 'l', + export type LEFT_RIGHT = typeof Direction.LEFT_RIGHT; // (undocumented) - RIGHT = 'r', + export type RIGHT_LEFT = typeof Direction.RIGHT_LEFT; + // (undocumented) + export type TOP_BOTTOM = typeof Direction.TOP_BOTTOM; } - export enum Ranker { - LONGEST_PATH = 'longest-path', - NETWORK_SIMPLEX = 'network-simplex', - TIGHT_TREE = 'tight-tree', + // (undocumented) + export type LabelPosition = + (typeof LabelPosition)[keyof typeof LabelPosition]; + // (undocumented) + export namespace LabelPosition { + // (undocumented) + export type CENTER = typeof LabelPosition.CENTER; + // (undocumented) + export type LEFT = typeof LabelPosition.LEFT; + // (undocumented) + export type RIGHT = typeof LabelPosition.RIGHT; } + const Direction: { + readonly TOP_BOTTOM: 'TB'; + readonly BOTTOM_TOP: 'BT'; + readonly LEFT_RIGHT: 'LR'; + readonly RIGHT_LEFT: 'RL'; + }; + // (undocumented) + export type Ranker = (typeof Ranker)[keyof typeof Ranker]; + // (undocumented) + export namespace Ranker { + // (undocumented) + export type LONGEST_PATH = typeof Ranker.LONGEST_PATH; + // (undocumented) + export type NETWORK_SIMPLEX = typeof Ranker.NETWORK_SIMPLEX; + // (undocumented) + export type TIGHT_TREE = typeof Ranker.TIGHT_TREE; + } + const Alignment: { + readonly UP_LEFT: 'UL'; + readonly UP_RIGHT: 'UR'; + readonly DOWN_LEFT: 'DL'; + readonly DOWN_RIGHT: 'DR'; + }; export type RenderEdgeFunction = ( props: RenderEdgeProps, ) => ReactNode; @@ -344,12 +380,22 @@ export namespace DependencyGraphTypes { name?: string | undefined; }; }; + const Ranker: { + readonly NETWORK_SIMPLEX: 'network-simplex'; + readonly TIGHT_TREE: 'tight-tree'; + readonly LONGEST_PATH: 'longest-path'; + }; export type RenderLabelFunction = ( props: RenderLabelProps, ) => ReactNode; export type RenderLabelProps = { edge: DependencyEdge; }; + const LabelPosition: { + readonly LEFT: 'l'; + readonly RIGHT: 'r'; + readonly CENTER: 'c'; + }; export type RenderNodeFunction = ( props: RenderNodeProps, ) => ReactNode; diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 4b99402c3a..476c458e60 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -79,7 +79,7 @@ export interface DependencyGraphProps */ nodes: Types.DependencyNode[]; /** - * Graph {@link DependencyGraphTypes.Direction | direction} + * Graph {@link DependencyGraphTypes.(Direction:namespace) | direction} * * @remarks * @@ -87,7 +87,7 @@ export interface DependencyGraphProps */ direction?: Types.Direction; /** - * Node {@link DependencyGraphTypes.Alignment | alignment} + * Node {@link DependencyGraphTypes.(Alignment:namespace) | alignment} */ align?: Types.Alignment; /** @@ -135,7 +135,7 @@ export interface DependencyGraphProps */ acyclicer?: 'greedy'; /** - * {@link DependencyGraphTypes.Ranker | Algorithm} used to rank nodes + * {@link DependencyGraphTypes.(Ranker:namespace) | Algorithm} used to rank nodes * * @remarks * @@ -143,7 +143,7 @@ export interface DependencyGraphProps */ ranker?: Types.Ranker; /** - * {@link DependencyGraphTypes.LabelPosition | Position} of label in relation to edge + * {@link DependencyGraphTypes.(LabelPosition:namespace) | Position} of label in relation to edge * * @remarks * @@ -533,7 +533,7 @@ export function DependencyGraph( height={graphHeight} y={maxHeight / 2 - graphHeight / 2} x={maxWidth / 2 - graphWidth / 2} - viewBox={`-25 -25 ${graphWidth + 50} ${graphHeight + 50}`} + viewBox={`0 0 ${graphWidth} ${graphHeight}`} > {graphEdges.map(e => { const edge = graph.current.edge(e) as GraphEdge; diff --git a/packages/core-components/src/components/DependencyGraph/types.ts b/packages/core-components/src/components/DependencyGraph/types.ts index 4436f8d1aa..6a75df27b5 100644 --- a/packages/core-components/src/components/DependencyGraph/types.ts +++ b/packages/core-components/src/components/DependencyGraph/types.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ /** * Types used to customize and provide data to {@link DependencyGraph} @@ -134,23 +136,38 @@ export namespace DependencyGraphTypes { * * @public */ - export enum Direction { + export const Direction = { /** * Top to Bottom */ - TOP_BOTTOM = 'TB', + TOP_BOTTOM: 'TB', /** * Bottom to Top */ - BOTTOM_TOP = 'BT', + BOTTOM_TOP: 'BT', /** * Left to Right */ - LEFT_RIGHT = 'LR', + LEFT_RIGHT: 'LR', /** * Right to Left */ - RIGHT_LEFT = 'RL', + RIGHT_LEFT: 'RL', + } as const; + + /** + * @public + */ + export type Direction = (typeof Direction)[keyof typeof Direction]; + + /** + * @public + */ + export namespace Direction { + export type TOP_BOTTOM = typeof Direction.TOP_BOTTOM; + export type BOTTOM_TOP = typeof Direction.BOTTOM_TOP; + export type LEFT_RIGHT = typeof Direction.LEFT_RIGHT; + export type RIGHT_LEFT = typeof Direction.RIGHT_LEFT; } /** @@ -158,23 +175,38 @@ export namespace DependencyGraphTypes { * * @public */ - export enum Alignment { + export const Alignment = { /** * Up Left */ - UP_LEFT = 'UL', + UP_LEFT: 'UL', /** * Up Right */ - UP_RIGHT = 'UR', + UP_RIGHT: 'UR', /** * Down Left */ - DOWN_LEFT = 'DL', + DOWN_LEFT: 'DL', /** * Down Right */ - DOWN_RIGHT = 'DR', + DOWN_RIGHT: 'DR', + } as const; + + /** + * @public + */ + export type Alignment = (typeof Alignment)[keyof typeof Alignment]; + + /** + * @public + */ + export namespace Alignment { + export type UP_LEFT = typeof Alignment.UP_LEFT; + export type UP_RIGHT = typeof Alignment.UP_RIGHT; + export type DOWN_LEFT = typeof Alignment.DOWN_LEFT; + export type DOWN_RIGHT = typeof Alignment.DOWN_RIGHT; } /** @@ -182,15 +214,15 @@ export namespace DependencyGraphTypes { * * @public */ - export enum Ranker { + export const Ranker = { /** * {@link https://en.wikipedia.org/wiki/Network_simplex_algorithm | Network Simplex} algorithm */ - NETWORK_SIMPLEX = 'network-simplex', + NETWORK_SIMPLEX: 'network-simplex', /** * Tight Tree algorithm */ - TIGHT_TREE = 'tight-tree', + TIGHT_TREE: 'tight-tree', /** * Longest path algorithm * @@ -198,7 +230,21 @@ export namespace DependencyGraphTypes { * * Simplest and fastest */ - LONGEST_PATH = 'longest-path', + LONGEST_PATH: 'longest-path', + } as const; + + /** + * @public + */ + export type Ranker = (typeof Ranker)[keyof typeof Ranker]; + + /** + * @public + */ + export namespace Ranker { + export type NETWORK_SIMPLEX = typeof Ranker.NETWORK_SIMPLEX; + export type TIGHT_TREE = typeof Ranker.TIGHT_TREE; + export type LONGEST_PATH = typeof Ranker.LONGEST_PATH; } /** @@ -206,9 +252,24 @@ export namespace DependencyGraphTypes { * * @public */ - export enum LabelPosition { - LEFT = 'l', - RIGHT = 'r', - CENTER = 'c', + export const LabelPosition = { + LEFT: 'l', + RIGHT: 'r', + CENTER: 'c', + } as const; + + /** + * @public + */ + export type LabelPosition = + (typeof LabelPosition)[keyof typeof LabelPosition]; + + /** + * @public + */ + export namespace LabelPosition { + export type LEFT = typeof LabelPosition.LEFT; + export type RIGHT = typeof LabelPosition.RIGHT; + export type CENTER = typeof LabelPosition.CENTER; } } diff --git a/packages/core-components/src/components/LogViewer/LogViewerControls.tsx b/packages/core-components/src/components/LogViewer/LogViewerControls.tsx index e920d623e8..461e058d2d 100644 --- a/packages/core-components/src/components/LogViewer/LogViewerControls.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewerControls.tsx @@ -15,17 +15,20 @@ */ import { KeyboardEvent } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import IconButton from '@material-ui/core/IconButton'; import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import FilterListIcon from '@material-ui/icons/FilterList'; +import { coreComponentsTranslationRef } from '../../translation'; import { LogViewerSearch } from './useLogViewerSearch'; export interface LogViewerControlsProps extends LogViewerSearch {} export function LogViewerControls(props: LogViewerControlsProps) { + const { t } = useTranslationRef(coreComponentsTranslationRef); const { resultCount, resultIndexStep, toggleShouldFilter } = props; const resultIndex = props.resultIndex ?? 0; @@ -57,7 +60,7 @@ export function LogViewerControls(props: LogViewerControlsProps) { props.setSearchInput(e.target.value)} diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx index e384a8e882..1936061b1c 100644 --- a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx @@ -21,7 +21,7 @@ import classnames from 'classnames'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { VariableSizeList, FixedSizeList } from 'react-window'; +import { FixedSizeList, VariableSizeList } from 'react-window'; import { AnsiLine, AnsiProcessor } from './AnsiProcessor'; import { LogLine } from './LogLine'; @@ -29,6 +29,7 @@ import { LogViewerControls } from './LogViewerControls'; import { HEADER_SIZE, useStyles } from './styles'; import { useLogViewerSearch } from './useLogViewerSearch'; import { useLogViewerSelection } from './useLogViewerSelection'; +import Snackbar from '@material-ui/core/Snackbar'; export interface RealLogViewerProps { text: string; @@ -47,6 +48,7 @@ export function RealLogViewer(props: RealLogViewerProps) { // The processor keeps state that optimizes appending to the text const processor = useMemo(() => new AnsiProcessor(), []); const lines = processor.process(props.text); + const [showCopyInfo, setShowCopyInfo] = useState(false); const search = useLogViewerSearch(lines); const selection = useLogViewerSelection(lines); @@ -69,19 +71,39 @@ export function RealLogViewer(props: RealLogViewerProps) { } }, [listInstance, search.resultLine, lines]); + useEffect(() => { + const hash = selection.getHash(); + if (hash.length > 0) { + history.replaceState(null, '', hash); + } + }, [selection]); + useEffect(() => { if (location.hash) { - // #line-6 -> 6 - const line = parseInt(location.hash.replace(/\D/g, ''), 10); - selection.setSelection(line, false); + selection.selectAll(location.hash); } }, []); // eslint-disable-line react-hooks/exhaustive-deps const handleSelectLine = ( line: number, - event: { shiftKey: boolean; preventDefault: () => void }, + event: { + shiftKey: boolean; + metaKey: boolean; + ctrlKey: boolean; + preventDefault: () => void; + }, ) => { - selection.setSelection(line, event.shiftKey); + event.preventDefault(); + selection.setSelection( + line, + event.shiftKey, + event.metaKey || event.ctrlKey, + ); + }; + + const handleCopySelection = (line: number) => { + selection.copySelection(line); + setShowCopyInfo(true); }; function setRowHeight(index: number, size: number) { @@ -97,90 +119,99 @@ export function RealLogViewer(props: RealLogViewerProps) { } return ( - - {({ height, width }: { height?: number; width?: number }) => { - const commonProps = { - ref: setListInstance, - className: classes.log, - height: (height || 480) - HEADER_SIZE, - width: width || 640, - itemData: search.lines, - itemCount: search.lines.length, - }; + <> + + {({ height, width }: { height?: number; width?: number }) => { + const commonProps = { + ref: setListInstance, + className: classes.log, + height: (height || 480) - HEADER_SIZE, + width: width || 640, + itemData: search.lines, + itemCount: search.lines.length, + }; - const renderItem = ({ - index, - style, - data, - }: { - index: number; - style: React.CSSProperties; - data: AnsiLine[]; - }) => { - const line = data[index]; - const { lineNumber } = line; - return ( - - {selection.shouldShowButton(lineNumber) && ( - selection.copySelection()} - > - - - )} -
    handleSelectLine(lineNumber, event)} - onKeyPress={event => handleSelectLine(lineNumber, event)} + const renderItem = ({ + index, + style, + data, + }: { + index: number; + style: React.CSSProperties; + data: AnsiLine[]; + }) => { + const line = data[index]; + const { lineNumber } = line; + return ( + - {lineNumber} - - + {selection.shouldShowCopyButton(lineNumber) && ( + handleCopySelection(lineNumber)} + > + + + )} + handleSelectLine(lineNumber, event)} + onKeyPress={event => handleSelectLine(lineNumber, event)} + > + {lineNumber} + + + + ); + }; + + return ( + + + + + {shouldTextWrap ? ( + + {...commonProps} + itemSize={getRowHeight} + > + {renderItem} + + ) : ( + {...commonProps} itemSize={20}> + {renderItem} + + )} ); - }; - - return ( - - - - - {shouldTextWrap ? ( - - {...commonProps} - itemSize={getRowHeight} - > - {renderItem} - - ) : ( - {...commonProps} itemSize={20}> - {renderItem} - - )} - - ); - }} - + }} + + setShowCopyInfo(false)} + message="Lines copied to clipboard" + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + /> + ); } diff --git a/packages/core-components/src/components/LogViewer/styles.ts b/packages/core-components/src/components/LogViewer/styles.ts index 76e045ca32..83fb3a84ad 100644 --- a/packages/core-components/src/components/LogViewer/styles.ts +++ b/packages/core-components/src/components/LogViewer/styles.ts @@ -89,14 +89,20 @@ export const useStyles = makeStyles( position: 'absolute', paddingTop: 0, paddingBottom: 0, + '&:hover': { + color: theme.palette.linkHover, + }, }, lineNumber: { display: 'inline-block', textAlign: 'end', width: 60, + paddingRight: theme.spacing(1), marginRight: theme.spacing(1), cursor: 'pointer', flexShrink: 0, + color: colors.blue[300], + borderRight: `1px solid ${colors.blue[700]}`, }, textHighlight: { background: alpha(theme.palette.info.main, 0.15), diff --git a/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx b/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx index 5314f4e7f6..13280d38a4 100644 --- a/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx +++ b/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx @@ -16,7 +16,7 @@ import { PropsWithChildren } from 'react'; import { act, renderHook } from '@testing-library/react'; -import { TestApiProvider, MockErrorApi } from '@backstage/test-utils'; +import { MockErrorApi, TestApiProvider } from '@backstage/test-utils'; import { errorApiRef } from '@backstage/core-plugin-api'; import { AnsiLine } from './AnsiProcessor'; import { useLogViewerSelection } from './useLogViewerSelection'; @@ -35,10 +35,26 @@ const lines = [ new AnsiLine(3, [{ text: '3', modifiers: {} }]), new AnsiLine(4, [{ text: '4', modifiers: {} }]), new AnsiLine(5, [{ text: '5', modifiers: {} }]), + new AnsiLine(6, [{ text: '6', modifiers: {} }]), + new AnsiLine(7, [{ text: '7', modifiers: {} }]), ]; +const expectSelectedLines = (rendered: any, selectedLines: number[]) => { + expect(rendered.result.current.isSelected(1)).toBe(selectedLines.includes(1)); + expect(rendered.result.current.isSelected(2)).toBe(selectedLines.includes(2)); + expect(rendered.result.current.isSelected(3)).toBe(selectedLines.includes(3)); + expect(rendered.result.current.isSelected(4)).toBe(selectedLines.includes(4)); + expect(rendered.result.current.isSelected(5)).toBe(selectedLines.includes(5)); + expect(rendered.result.current.isSelected(6)).toBe(selectedLines.includes(6)); + expect(rendered.result.current.isSelected(7)).toBe(selectedLines.includes(7)); +}; + describe('useLogViewerSelection', () => { - it('should manage a selection', () => { + beforeEach(() => { + (copyToClipboard as jest.Mock).mockClear(); + }); + + it('should select a new line when clicked', () => { const rendered = renderHook(() => useLogViewerSelection(lines), { wrapper: ({ children }: PropsWithChildren<{}>) => ( @@ -47,77 +63,290 @@ describe('useLogViewerSelection', () => { ), }); - expect(rendered.result.current.isSelected(1)).toBe(false); - expect(rendered.result.current.isSelected(2)).toBe(false); - expect(rendered.result.current.isSelected(3)).toBe(false); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, false, false)); + expectSelectedLines(rendered, [5]); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + }); - expect(rendered.result.current.shouldShowButton(1)).toBe(false); - expect(rendered.result.current.shouldShowButton(2)).toBe(false); - expect(rendered.result.current.shouldShowButton(3)).toBe(false); + it('should deselect a selected line when clicked', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - act(() => rendered.result.current.setSelection(2, false)); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, []); + }); - expect(rendered.result.current.isSelected(1)).toBe(false); - expect(rendered.result.current.isSelected(2)).toBe(true); - expect(rendered.result.current.isSelected(3)).toBe(false); + it('should select a new line on shift+click if nothing is selected', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - expect(rendered.result.current.shouldShowButton(1)).toBe(false); - expect(rendered.result.current.shouldShowButton(2)).toBe(true); - expect(rendered.result.current.shouldShowButton(3)).toBe(false); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2]); + }); - act(() => rendered.result.current.setSelection(3, false)); + it('should deselect a single line on shift+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - expect(rendered.result.current.isSelected(1)).toBe(false); - expect(rendered.result.current.isSelected(2)).toBe(false); - expect(rendered.result.current.isSelected(3)).toBe(true); - expect(rendered.result.current.isSelected(4)).toBe(false); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, []); + }); - expect(rendered.result.current.shouldShowButton(1)).toBe(false); - expect(rendered.result.current.shouldShowButton(2)).toBe(false); - expect(rendered.result.current.shouldShowButton(3)).toBe(true); - expect(rendered.result.current.shouldShowButton(4)).toBe(false); + it('should select a range below on shift+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - act(() => rendered.result.current.setSelection(1, true)); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5]); + }); - expect(rendered.result.current.isSelected(1)).toBe(true); - expect(rendered.result.current.isSelected(2)).toBe(true); - expect(rendered.result.current.isSelected(3)).toBe(true); - expect(rendered.result.current.isSelected(4)).toBe(false); + it('should select a range above on shift+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - expect(rendered.result.current.shouldShowButton(1)).toBe(true); - expect(rendered.result.current.shouldShowButton(2)).toBe(false); - expect(rendered.result.current.shouldShowButton(3)).toBe(true); - expect(rendered.result.current.shouldShowButton(4)).toBe(false); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [5]); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5]); + }); - act(() => rendered.result.current.setSelection(4, true)); + it('should reduce a selection on shift+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); - expect(rendered.result.current.isSelected(1)).toBe(false); - expect(rendered.result.current.isSelected(2)).toBe(false); - expect(rendered.result.current.isSelected(3)).toBe(true); - expect(rendered.result.current.isSelected(4)).toBe(true); - expect(rendered.result.current.isSelected(5)).toBe(false); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(7, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5, 6, 7]); + act(() => rendered.result.current.setSelection(4, true, false)); + expectSelectedLines(rendered, [2, 3, 4]); + act(() => rendered.result.current.setSelection(2, true, false)); + expectSelectedLines(rendered, [2]); + }); - expect(rendered.result.current.shouldShowButton(1)).toBe(false); - expect(rendered.result.current.shouldShowButton(2)).toBe(false); - expect(rendered.result.current.shouldShowButton(3)).toBe(true); - expect(rendered.result.current.shouldShowButton(4)).toBe(true); - expect(rendered.result.current.shouldShowButton(5)).toBe(false); + it('should add a new selection on cmd/ctrl+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, true)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, false, true)); + expectSelectedLines(rendered, [2, 5]); + }); + + it('should merge selections on cmd/ctrl+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(1, false, true)); + expectSelectedLines(rendered, [1]); + act(() => rendered.result.current.setSelection(3, true, false)); + expectSelectedLines(rendered, [1, 2, 3]); + act(() => rendered.result.current.setSelection(5, false, true)); + expectSelectedLines(rendered, [1, 2, 3, 5]); + act(() => rendered.result.current.setSelection(7, true, false)); + expectSelectedLines(rendered, [1, 2, 3, 5, 6, 7]); + act(() => rendered.result.current.setSelection(4, false, true)); + expectSelectedLines(rendered, [1, 2, 3, 4, 5, 6, 7]); + }); + + it('should split a selection on cmd/ctrl+click', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(1, false, true)); + expectSelectedLines(rendered, [1]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [1, 2, 3, 4, 5]); + act(() => rendered.result.current.setSelection(3, false, true)); + expectSelectedLines(rendered, [1, 2, 4, 5]); + }); + + it('should copy a selected line', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); expect(copyToClipboard).not.toHaveBeenCalled(); - act(() => rendered.result.current.copySelection()); - expect(copyToClipboard).toHaveBeenLastCalledWith('3\n4'); + act(() => rendered.result.current.copySelection(2)); + expect(copyToClipboard).toHaveBeenLastCalledWith('2'); + }); - act(() => rendered.result.current.setSelection(2, true)); - act(() => rendered.result.current.setSelection(4, true)); + it('should copy a selected range', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + rendered.rerender(); - act(() => rendered.result.current.copySelection()); - expect(copyToClipboard).toHaveBeenCalledWith('2\n3\n4'); + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5]); - act(() => rendered.result.current.setSelection(2, false)); - act(() => rendered.result.current.setSelection(4, false)); - act(() => rendered.result.current.setSelection(4, false)); - act(() => rendered.result.current.setSelection(5, true)); - act(() => rendered.result.current.copySelection()); - expect(copyToClipboard).toHaveBeenCalledWith('5'); + expect(copyToClipboard).not.toHaveBeenCalled(); + act(() => rendered.result.current.copySelection(2)); + expect(copyToClipboard).toHaveBeenCalledWith('2\n3\n4\n5'); + }); + + it('should copy the correct selection', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + rendered.rerender(); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5]); + + act(() => rendered.result.current.setSelection(7, false, true)); + expectSelectedLines(rendered, [2, 3, 4, 5, 7]); + + expect(copyToClipboard).not.toHaveBeenCalled(); + act(() => rendered.result.current.copySelection(2)); + expect(copyToClipboard).toHaveBeenCalledWith('2\n3\n4\n5'); + act(() => rendered.result.current.copySelection(7)); + expect(copyToClipboard).toHaveBeenCalledWith('7'); + }); + + it('should add a single line selection to the hash', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + rendered.rerender(); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + + expect(rendered.result.current.getHash()).toBe('#lines-2'); + }); + + it('should add a range selection to the hash', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + rendered.rerender(); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(2, false, false)); + expectSelectedLines(rendered, [2]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [2, 3, 4, 5]); + + expect(rendered.result.current.getHash()).toBe('#lines-2-5'); + }); + + it('should add multiple selections to the hash', () => { + const rendered = renderHook(() => useLogViewerSelection(lines), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }); + rendered.rerender(); + + expectSelectedLines(rendered, []); + act(() => rendered.result.current.setSelection(3, false, false)); + expectSelectedLines(rendered, [3]); + act(() => rendered.result.current.setSelection(5, true, false)); + expectSelectedLines(rendered, [3, 4, 5]); + act(() => rendered.result.current.setSelection(1, false, true)); + expectSelectedLines(rendered, [1, 3, 4, 5]); + act(() => rendered.result.current.setSelection(7, false, true)); + expectSelectedLines(rendered, [1, 3, 4, 5, 7]); + + expect(rendered.result.current.getHash()).toBe('#lines-3-5,1,7'); }); }); diff --git a/packages/core-components/src/components/LogViewer/useLogViewerSelection.tsx b/packages/core-components/src/components/LogViewer/useLogViewerSelection.tsx index 97fee53c8a..d59a552777 100644 --- a/packages/core-components/src/components/LogViewer/useLogViewerSelection.tsx +++ b/packages/core-components/src/components/LogViewer/useLogViewerSelection.tsx @@ -19,11 +19,14 @@ import { useEffect, useState } from 'react'; import useCopyToClipboard from 'react-use/esm/useCopyToClipboard'; import { AnsiLine } from './AnsiProcessor'; +type Selection = { + start: number; + end: number; +}; + export function useLogViewerSelection(lines: AnsiLine[]) { const errorApi = useApi(errorApiRef); - const [sel, setSelection] = useState<{ start: number; end: number }>(); - const start = sel ? Math.min(sel.start, sel.end) : undefined; - const end = sel ? Math.max(sel.start, sel.end) : undefined; + const [selections, setSelections] = useState([]); const [{ error }, copyToClipboard] = useCopyToClipboard(); @@ -33,38 +36,202 @@ export function useLogViewerSelection(lines: AnsiLine[]) { } }, [error, errorApi]); + const findClosestSelection = ( + allSelections: Selection[], + line: number, + ): Selection | undefined => { + if (selections.length === 0) { + return undefined; + } + let minDistance = Number.MAX_SAFE_INTEGER; + let closestSelection: Selection | undefined = undefined; + + allSelections.forEach(s => { + const distance = Math.min( + Math.abs(s.start - line), + Math.abs(s.end - line), + ); + if (distance < minDistance) { + minDistance = distance; + closestSelection = s; + } + }); + + return closestSelection; + }; + + const mergeNeighbouringSelections = ( + allSelections: Selection[], + line: number, + ): Selection[] => { + // Merge selections if they're next to each other + const neighboringSelections = allSelections.filter( + s => s.start - 1 === line || s.end + 1 === line, + ); + if (neighboringSelections.length === 0) { + return allSelections; + } + const newSelection = { + start: Math.min(line, ...neighboringSelections.map(s => s.start)), + end: Math.max(line, ...neighboringSelections.map(s => s.end)), + }; + + return [ + ...allSelections.filter( + s => + !neighboringSelections.includes(s) && + !(s.start === line && s.end === line), + ), + newSelection, + ]; + }; + return { - shouldShowButton(line: number) { - return start === line || end === line; + shouldShowCopyButton(line: number) { + // show copy button at the beginning of each selection + return selections.some(s => s.start === line); }, isSelected(line: number) { - if (!sel) { + if (!selections) { return false; } - return start! <= line && line <= end!; + // check if line is in any selection range + return selections.some( + s => s.start <= line && (s.end ?? s.start) >= line, + ); }, - setSelection(line: number, add: boolean) { - if (add) { - setSelection(s => - s ? { start: s.start, end: line } : { start: line, end: line }, + setSelection(line: number, addRange: boolean, addNewSelection: boolean) { + setSelections(currentSelections => { + const clickedSelection = currentSelections.find( + s => s.start <= line && s.end >= line, ); - } else { - setSelection(s => - s?.start === line && s?.end === line - ? undefined - : { start: line, end: line }, + const otherSelections = currentSelections.filter( + s => s !== clickedSelection, ); - } + + if (!addRange && !addNewSelection) { + // Normal click -> select only this line if nothing or multiple lines are selected + if ( + !clickedSelection || + clickedSelection.start !== clickedSelection.end + ) { + return [{ start: line, end: line }]; + } + // Clear selection if single line is selected + return []; + } + + if (addRange) { + // Shift+click -> extend/reduce selection + if (currentSelections.length === 0) { + // No existing selection -> create new selection + return [{ start: line, end: line }]; + } + + if (clickedSelection) { + // Clicked inside an existing selection -> reduce selection + if (clickedSelection.start === clickedSelection.end) { + // Single line selection -> remove it + return otherSelections; + } + // Reduce selection + return [ + ...otherSelections, + { start: clickedSelection.start, end: line }, + ]; + } + + // Extend the closest selection to the new line + const closestSelection = findClosestSelection( + currentSelections, + line, + ); + if (!closestSelection) { + // Can't actually happen + return currentSelections; + } + if (closestSelection.start < line) { + // Add lines before the selection + return mergeNeighbouringSelections( + [ + ...otherSelections.filter(s => s !== closestSelection), + { start: closestSelection.start, end: line }, + ], + line, + ); + } + // Add lines after the selection + return mergeNeighbouringSelections( + [ + ...otherSelections.filter(s => s !== closestSelection), + { start: line, end: closestSelection!.end }, + ], + line, + ); + } + + if (addNewSelection) { + // Ctrl/Cmd+click -> add new selection + if (!clickedSelection) { + // Just add new selection + return mergeNeighbouringSelections( + [...currentSelections, { start: line, end: line }], + line, + ); + } + if (clickedSelection.start === clickedSelection.end) { + // Single line selection -> remove it + return otherSelections; + } + // Multi line selection -> split it + return [ + ...otherSelections, + ...(clickedSelection.start < line + ? [{ start: clickedSelection.start, end: line - 1 }] + : []), + ...(clickedSelection.end > line + ? [{ start: line + 1, end: clickedSelection.end }] + : []), + ]; + } + + return []; + }); }, - copySelection() { - if (sel) { - const copyText = lines - .slice(Math.min(sel.start, sel.end) - 1, Math.max(sel.start, sel.end)) - .map(l => l.chunks.map(c => c.text).join('')) - .join('\n'); - copyToClipboard(copyText); - setSelection(undefined); + copySelection(line: number) { + const selection = selections.find(s => s.start === line); + if (!selection) { + return; } + const copyText = lines + .slice(selection.start - 1, selection.end) + .map(l => l.chunks.map(c => c.text).join('')) + .join('\n'); + copyToClipboard(copyText); + }, + getHash() { + if (selections.length === 0) { + return ''; + } + const parts = selections.map(s => { + if (s.start === s.end) { + return `${s.start}`; + } + return `${s.start}-${s.end}`; + }); + return `#lines-${parts.join(',')}`; + }, + selectAll(hash: string) { + const match = hash.match(/#lines-([\d,-]+)/); + const s: Selection[] = []; + if (match) { + const ranges = match[1].split(','); + ranges.forEach(r => { + const [start, end] = r.split('-').map(Number); + s.push({ start, end: end ?? start }); + }); + } + setSelections(s); }, }; } diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 68499e2dc1..7fc45d767d 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -23,6 +23,7 @@ type Props = { title?: TooltipProps['title']; line?: number | undefined; placement?: TooltipProps['placement']; + tooltipClasses?: TooltipProps['classes']; }; export type OverflowTooltipClassKey = 'container'; @@ -51,6 +52,7 @@ export function OverflowTooltip(props: Props) { {props.text} diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index 7d9c4d472d..2e05784fd6 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -96,6 +96,7 @@ const useStyles = makeStyles( fontWeight: theme.typography.fontWeightBold, }, markdownContent: { + wordBreak: 'break-word', '& p': { display: 'inline', }, diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 00fe6f96ae..bfa1e15d5e 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -97,11 +97,11 @@ const useStyles = makeStyles( { name: 'BackstageSidebar' }, ); -enum State { - Closed, - Idle, - Open, -} +const State = { + Closed: 0, + Idle: 1, + Open: 2, +} as const; /** @public */ export type SidebarProps = { @@ -144,7 +144,9 @@ const DesktopSidebar = (props: DesktopSidebarProps) => { theme => theme.breakpoints.down('md'), { noSsr: true }, ); - const [state, setState] = useState(State.Closed); + const [state, setState] = useState<(typeof State)[keyof typeof State]>( + State.Closed, + ); const hoverTimerRef = useRef(); const { isPinned, toggleSidebarPinState } = useSidebarPinState(); diff --git a/packages/core-components/src/layout/Sidebar/localStorage.ts b/packages/core-components/src/layout/Sidebar/localStorage.ts index 78ec355306..b62252e1df 100644 --- a/packages/core-components/src/layout/Sidebar/localStorage.ts +++ b/packages/core-components/src/layout/Sidebar/localStorage.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -enum LocalStorageKeys { - SIDEBAR_PIN_STATE = 'sidebarPinState', -} +const LocalStorageKeys = { + SIDEBAR_PIN_STATE: 'sidebarPinState', +} as const; export const LocalStorage = { getSidebarPinState(): boolean { diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index aa644fa909..3d0e62747e 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -127,5 +127,10 @@ export const coreComponentsTranslationRef = createTranslationRef({ title: 'You do not appear to be signed in. Please try reloading the browser page.', }, + logViewer: { + searchField: { + placeholder: 'Search', + }, + }, }, }); diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 98ffb44043..a47e87fe66 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/core-plugin-api +## 1.12.1-next.0 + +### 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-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.12.0 + +### Minor Changes + +- 83439b1: All route references are now forwards compatible with the new frontend system, i.e. `@backstage/frontend-plugin-api`. This means they no longer need to be converted with `convertLegacyRouteRef` or `convertLegacyRouteRefs` from `@backstage/core-compat-api`. + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + +## 1.11.2-next.1 + +### Patch Changes + +- b2bef92: Convert all enums to erasable-syntax compliant patterns + ## 1.11.2-next.0 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index c80b6af1e8..11e4a911c5 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.11.2-next.0", + "version": "1.12.1-next.0", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" @@ -51,9 +51,11 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", - "history": "^5.0.0" + "history": "^5.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/core-plugin-api/report-alpha.api.md b/packages/core-plugin-api/report-alpha.api.md index 6243c76cff..b0aea73868 100644 --- a/packages/core-plugin-api/report-alpha.api.md +++ b/packages/core-plugin-api/report-alpha.api.md @@ -3,253 +3,54 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; -import { Expand } from '@backstage/types'; -import { ExpandRecursive } from '@backstage/types'; -import { JSX as JSX_2 } from 'react'; -import { Observable } from '@backstage/types'; -import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; -import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; +import { AppLanguageApi } from '@backstage/frontend-plugin-api'; +import { appLanguageApiRef } from '@backstage/frontend-plugin-api'; +import { createTranslationMessages } from '@backstage/frontend-plugin-api'; +import { createTranslationRef } from '@backstage/frontend-plugin-api'; +import { createTranslationResource } from '@backstage/frontend-plugin-api'; +import { TranslationApi } from '@backstage/frontend-plugin-api'; +import { translationApiRef } from '@backstage/frontend-plugin-api'; +import { TranslationFunction } from '@backstage/frontend-plugin-api'; +import { TranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationMessagesOptions } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; +import { TranslationRefOptions } from '@backstage/frontend-plugin-api'; +import { TranslationResource } from '@backstage/frontend-plugin-api'; +import { TranslationResourceOptions } from '@backstage/frontend-plugin-api'; +import { TranslationSnapshot } from '@backstage/frontend-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; -// @alpha (undocumented) -export type AppLanguageApi = { - getAvailableLanguages(): { - languages: string[]; - }; - setLanguage(language?: string): void; - getLanguage(): { - language: string; - }; - language$(): Observable<{ - language: string; - }>; -}; +export { AppLanguageApi }; -// @alpha (undocumented) -export const appLanguageApiRef: ApiRef; +export { appLanguageApiRef }; -// @alpha -export function createTranslationMessages< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TFull extends boolean, ->( - options: TranslationMessagesOptions, -): TranslationMessages; +export { createTranslationMessages }; -// @alpha (undocumented) -export function createTranslationRef< - TId extends string, - const TNestedMessages extends AnyNestedMessages, - TTranslations extends { - [language in string]: () => Promise<{ - default: { - [key in keyof FlattenedMessages]: string | null; - }; - }>; - }, ->( - config: TranslationRefOptions, -): TranslationRef>; +export { createTranslationRef }; -// @alpha (undocumented) -export function createTranslationResource< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TTranslations extends { - [language in string]: () => Promise<{ - default: - | TranslationMessages_2 - | { - [key in keyof TMessages]: string | null; - }; - }>; - }, ->( - options: TranslationResourceOptions, -): TranslationResource; +export { createTranslationResource }; -// @alpha (undocumented) -export type TranslationApi = { - getTranslation< - TMessages extends { - [key in string]: string; - }, - >( - translationRef: TranslationRef, - ): TranslationSnapshot; - translation$< - TMessages extends { - [key in string]: string; - }, - >( - translationRef: TranslationRef, - ): Observable>; -}; +export { TranslationApi }; -// @alpha (undocumented) -export const translationApiRef: ApiRef; +export { translationApiRef }; -// @alpha (undocumented) -export type TranslationFunction< - TMessages extends { - [key in string]: string; - }, -> = CollapsedMessages extends infer IMessages extends { - [key in string]: string; -} - ? { - ( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys, - PluralKeys, - IMessages, - string - > - ): IMessages[TKey]; - ( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys, - PluralKeys, - IMessages, - string | JSX_2.Element - > - ): JSX_2.Element; - } - : never; +export { TranslationFunction }; -// @alpha -export interface TranslationMessages< - TId extends string = string, - TMessages extends { - [key in string]: string; - } = { - [key in string]: string; - }, - TFull extends boolean = boolean, -> { - // (undocumented) - $$type: '@backstage/TranslationMessages'; - full: TFull; - id: TId; - messages: TMessages; -} +export { TranslationMessages }; -// @alpha -export interface TranslationMessagesOptions< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TFull extends boolean, -> { - // (undocumented) - full?: TFull; - // (undocumented) - messages: false extends TFull - ? { - [key in keyof TMessages]?: string | null; - } - : { - [key in keyof TMessages]: string | null; - }; - // (undocumented) - ref: TranslationRef_2; -} +export { TranslationMessagesOptions }; -// @alpha (undocumented) -export interface TranslationRef< - TId extends string = string, - TMessages extends { - [key in string]: string; - } = { - [key in string]: string; - }, -> { - // (undocumented) - $$type: '@backstage/TranslationRef'; - // (undocumented) - id: TId; - // (undocumented) - T: TMessages; -} +export { TranslationRef }; -// @alpha (undocumented) -export interface TranslationRefOptions< - TId extends string, - TNestedMessages extends AnyNestedMessages, - TTranslations extends { - [language in string]: () => Promise<{ - default: { - [key in keyof FlattenedMessages]: string | null; - }; - }>; - }, -> { - // (undocumented) - id: TId; - // (undocumented) - messages: TNestedMessages; - // (undocumented) - translations?: TTranslations; -} +export { TranslationRefOptions }; -// @alpha (undocumented) -export interface TranslationResource { - // (undocumented) - $$type: '@backstage/TranslationResource'; - // (undocumented) - id: TId; -} +export { TranslationResource }; -// @alpha (undocumented) -export interface TranslationResourceOptions< - TId extends string, - TMessages extends { - [key in string]: string; - }, - TTranslations extends { - [language in string]: () => Promise<{ - default: - | TranslationMessages_2 - | { - [key in keyof TMessages]: string | null; - }; - }>; - }, -> { - // (undocumented) - ref: TranslationRef_2; - // (undocumented) - translations: TTranslations; -} +export { TranslationResourceOptions }; -// @alpha (undocumented) -export type TranslationSnapshot< - TMessages extends { - [key in string]: string; - }, -> = - | { - ready: false; - } - | { - ready: true; - t: TranslationFunction; - }; +export { TranslationSnapshot }; -// @alpha (undocumented) -export const useTranslationRef: ( - translationRef: TranslationRef, -) => { - t: TranslationFunction; -}; +export { useTranslationRef }; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-plugin-api/report.api.md b/packages/core-plugin-api/report.api.md index b40088dc78..dc5a042488 100644 --- a/packages/core-plugin-api/report.api.md +++ b/packages/core-plugin-api/report.api.md @@ -3,33 +3,87 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AlertApi } from '@backstage/frontend-plugin-api'; +import { alertApiRef } from '@backstage/frontend-plugin-api'; +import { AlertMessage } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyApiRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiHolder } from '@backstage/frontend-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; +import { ApiRefConfig } from '@backstage/frontend-plugin-api'; +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { AppThemeApi } from '@backstage/frontend-plugin-api'; +import { appThemeApiRef } from '@backstage/frontend-plugin-api'; +import { atlassianAuthApiRef } from '@backstage/frontend-plugin-api'; +import { AuthProviderInfo } from '@backstage/frontend-plugin-api'; +import { AuthRequestOptions } from '@backstage/frontend-plugin-api'; +import { BackstageIdentityApi } from '@backstage/frontend-plugin-api'; +import { BackstageIdentityResponse } from '@backstage/frontend-plugin-api'; import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api'; +import { BackstageUserIdentity } from '@backstage/frontend-plugin-api'; +import { bitbucketAuthApiRef } from '@backstage/frontend-plugin-api'; +import { bitbucketServerAuthApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; -import { Config } from '@backstage/config'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { configApiRef } from '@backstage/frontend-plugin-api'; +import { createApiFactory } from '@backstage/frontend-plugin-api'; +import { createApiRef } from '@backstage/frontend-plugin-api'; +import { DiscoveryApi } from '@backstage/frontend-plugin-api'; +import { discoveryApiRef } from '@backstage/frontend-plugin-api'; +import { ErrorApi } from '@backstage/frontend-plugin-api'; +import { ErrorApiError } from '@backstage/frontend-plugin-api'; +import { ErrorApiErrorContext } from '@backstage/frontend-plugin-api'; +import { errorApiRef } from '@backstage/frontend-plugin-api'; +import { FeatureFlag } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsApi } from '@backstage/frontend-plugin-api'; +import { featureFlagsApiRef } from '@backstage/frontend-plugin-api'; +import { FeatureFlagsSaveOptions } from '@backstage/frontend-plugin-api'; +import { FeatureFlagState } from '@backstage/frontend-plugin-api'; +import { FetchApi } from '@backstage/frontend-plugin-api'; +import { fetchApiRef } from '@backstage/frontend-plugin-api'; +import { githubAuthApiRef } from '@backstage/frontend-plugin-api'; +import { gitlabAuthApiRef } from '@backstage/frontend-plugin-api'; +import { googleAuthApiRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; +import { IdentityApi } from '@backstage/frontend-plugin-api'; import { IdentityApi as IdentityApi_2 } from '@backstage/core-plugin-api'; -import { JsonValue } from '@backstage/types'; +import { identityApiRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { Observable } from '@backstage/types'; +import { microsoftAuthApiRef } from '@backstage/frontend-plugin-api'; +import { OAuthApi } from '@backstage/frontend-plugin-api'; +import { OAuthRequestApi } from '@backstage/frontend-plugin-api'; +import { oauthRequestApiRef } from '@backstage/frontend-plugin-api'; +import { OAuthRequester } from '@backstage/frontend-plugin-api'; +import { OAuthRequesterOptions } from '@backstage/frontend-plugin-api'; +import { OAuthScope } from '@backstage/frontend-plugin-api'; +import { oktaAuthApiRef } from '@backstage/frontend-plugin-api'; +import { oneloginAuthApiRef } from '@backstage/frontend-plugin-api'; +import { OpenIdConnectApi } from '@backstage/frontend-plugin-api'; +import { openshiftAuthApiRef } from '@backstage/frontend-plugin-api'; +import { PendingOAuthRequest } from '@backstage/frontend-plugin-api'; +import { ProfileInfo } from '@backstage/frontend-plugin-api'; +import { ProfileInfoApi } from '@backstage/frontend-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; +import { SessionApi } from '@backstage/frontend-plugin-api'; +import { SessionState } from '@backstage/frontend-plugin-api'; +import { StorageApi } from '@backstage/frontend-plugin-api'; +import { storageApiRef } from '@backstage/frontend-plugin-api'; +import { StorageValueSnapshot } from '@backstage/frontend-plugin-api'; +import { TypesToApiRefs } from '@backstage/frontend-plugin-api'; +import { useApi } from '@backstage/frontend-plugin-api'; +import { useApiHolder } from '@backstage/frontend-plugin-api'; +import { vmwareCloudAuthApiRef } from '@backstage/frontend-plugin-api'; +import { withApis } from '@backstage/frontend-plugin-api'; -// @public -export type AlertApi = { - post(alert: AlertMessage): void; - alert$(): Observable; -}; +export { AlertApi }; -// @public -export const alertApiRef: ApiRef; +export { alertApiRef }; -// @public -export type AlertMessage = { - message: string; - severity?: 'success' | 'info' | 'warning' | 'error'; - display?: 'permanent' | 'transient'; -}; +export { AlertMessage }; // @public export type AnalyticsApi = { @@ -76,17 +130,9 @@ export type AnalyticsTracker = { ) => void; }; -// @public -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { - [key in string]: unknown; - } ->; +export { AnyApiFactory }; -// @public -export type AnyApiRef = ApiRef; +export { AnyApiRef }; // @public export type AnyExternalRoutes = { @@ -108,34 +154,13 @@ export type AnyRoutes = { [name: string]: RouteRef | SubRouteRef; }; -// @public -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { - [name in string]: unknown; - }, -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; +export { ApiFactory }; -// @public -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; +export { ApiHolder }; -// @public -export type ApiRef = { - id: string; - T: T; -}; +export { ApiRef }; -// @public -export type ApiRefConfig = { - id: string; -}; +export { ApiRefConfig }; // @public export type AppComponents = { @@ -160,30 +185,13 @@ export type AppContext = { getComponents(): AppComponents; }; -// @public -export type AppTheme = { - id: string; - title: string; - variant: 'light' | 'dark'; - icon?: React.ReactElement; - Provider(props: { children: ReactNode }): JSX.Element | null; -}; +export { AppTheme }; -// @public -export type AppThemeApi = { - getInstalledThemes(): AppTheme[]; - activeThemeId$(): Observable; - getActiveThemeId(): string | undefined; - setActiveThemeId(themeId?: string): void; -}; +export { AppThemeApi }; -// @public -export const appThemeApiRef: ApiRef; +export { appThemeApiRef }; -// @public -export const atlassianAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { atlassianAuthApiRef }; // @public export function attachComponentData

    ( @@ -192,33 +200,13 @@ export function attachComponentData

    ( data: unknown, ): void; -// @public -export type AuthProviderInfo = { - id: string; - title: string; - icon: IconComponent; - message?: string; -}; +export { AuthProviderInfo }; -// @public -export type AuthRequestOptions = { - optional?: boolean; - instantPopup?: boolean; -}; +export { AuthRequestOptions }; -// @public -export type BackstageIdentityApi = { - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; +export { BackstageIdentityApi }; -// @public -export type BackstageIdentityResponse = { - token: string; - expiresAt?: Date; - identity: BackstageUserIdentity; -}; +export { BackstageIdentityResponse }; // @public export type BackstagePlugin< @@ -234,22 +222,11 @@ export type BackstagePlugin< externalRoutes: ExternalRoutes; }; -// @public -export type BackstageUserIdentity = { - type: 'user'; - userEntityRef: string; - ownershipEntityRefs: string[]; -}; +export { BackstageUserIdentity }; -// @public -export const bitbucketAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { bitbucketAuthApiRef }; -// @public -export const bitbucketServerAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { bitbucketServerAuthApiRef }; // @public export type BootErrorPageProps = PropsWithChildren<{ @@ -273,29 +250,13 @@ export type ComponentLoader = sync: T; }; -// @public -export type ConfigApi = Config; +export { ConfigApi }; -// @public -export const configApiRef: ApiRef; +export { configApiRef }; -// @public -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { - [name in string]: unknown; - }, ->(factory: ApiFactory): ApiFactory; +export { createApiFactory }; -// @public -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; - -// @public -export function createApiRef(config: ApiRefConfig): ApiRef; +export { createApiRef }; // @public export function createComponentExtension< @@ -363,13 +324,9 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; -// @public -export type DiscoveryApi = { - getBaseUrl(pluginId: string): Promise; -}; +export { DiscoveryApi }; -// @public -export const discoveryApiRef: ApiRef; +export { discoveryApiRef }; // @public export interface ElementCollection { @@ -385,29 +342,13 @@ export interface ElementCollection { }): ElementCollection; } -// @public -export type ErrorApi = { - post(error: ErrorApiError, context?: ErrorApiErrorContext): void; - error$(): Observable<{ - error: ErrorApiError; - context?: ErrorApiErrorContext; - }>; -}; +export { ErrorApi }; -// @public -export type ErrorApiError = { - name: string; - message: string; - stack?: string; -}; +export { ErrorApiError }; -// @public -export type ErrorApiErrorContext = { - hidden?: boolean; -}; +export { ErrorApiErrorContext }; -// @public -export const errorApiRef: ApiRef; +export { errorApiRef }; // @public export type ErrorBoundaryFallbackProps = PropsWithChildren<{ @@ -429,50 +370,28 @@ export type ExternalRouteRef< $$routeRefType: 'external'; params: ParamKeys; optional?: Optional; + readonly $$type: '@backstage/ExternalRouteRef'; + readonly T: Params; }; -// @public -export type FeatureFlag = { - name: string; - pluginId: string; - description?: string; -}; +export { FeatureFlag }; -// @public -export interface FeatureFlagsApi { - getRegisteredFlags(): FeatureFlag[]; - isActive(name: string): boolean; - registerFlag(flag: FeatureFlag): void; - save(options: FeatureFlagsSaveOptions): void; -} +export { FeatureFlagsApi }; -// @public -export const featureFlagsApiRef: ApiRef; +export { featureFlagsApiRef }; // @public export type FeatureFlagsHooks = { register(name: string): void; }; -// @public -export type FeatureFlagsSaveOptions = { - states: Record; - merge?: boolean; -}; +export { FeatureFlagsSaveOptions }; -// @public -export enum FeatureFlagState { - Active = 1, - None = 0, -} +export { FeatureFlagState }; -// @public -export type FetchApi = { - fetch: typeof fetch; -}; +export { FetchApi }; -// @public -export const fetchApiRef: ApiRef; +export { fetchApiRef }; // @public export function getComponentData( @@ -480,46 +399,17 @@ export function getComponentData( type: string, ): T | undefined; -// @public -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { githubAuthApiRef }; -// @public -export const gitlabAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { gitlabAuthApiRef }; -// @public -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { googleAuthApiRef }; -// @public -export type IconComponent = ComponentType<{ - fontSize?: 'medium' | 'large' | 'small' | 'inherit'; -}>; +export { IconComponent }; -// @public -export type IdentityApi = { - getProfileInfo(): Promise; - getBackstageIdentity(): Promise; - getCredentials(): Promise<{ - token?: string; - }>; - signOut(): Promise; -}; +export { IdentityApi }; -// @public -export const identityApiRef: ApiRef; +export { identityApiRef }; // @public @deprecated export type MakeSubRouteRef< @@ -539,75 +429,27 @@ export type MergeParams< P2 extends AnyParams, > = (P1[keyof P1] extends never ? {} : P1) & (P2 extends undefined ? {} : P2); -// @public -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { microsoftAuthApiRef }; -// @public -export type OAuthApi = { - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; +export { OAuthApi }; -// @public -export type OAuthRequestApi = { - createAuthRequester( - options: OAuthRequesterOptions, - ): OAuthRequester; - authRequest$(): Observable; -}; +export { OAuthRequestApi }; -// @public -export const oauthRequestApiRef: ApiRef; +export { oauthRequestApiRef }; -// @public -export type OAuthRequester = ( - scopes: Set, -) => Promise; +export { OAuthRequester }; -// @public -export type OAuthRequesterOptions = { - provider: AuthProviderInfo; - onAuthRequest(scopes: Set): Promise; -}; +export { OAuthRequesterOptions }; -// @public -export type OAuthScope = string | string[]; +export { OAuthScope }; -// @public -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { oktaAuthApiRef }; -// @public -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { oneloginAuthApiRef }; -// @public -export type OpenIdConnectApi = { - getIdToken(options?: AuthRequestOptions): Promise; -}; +export { OpenIdConnectApi }; -// @public -export const openshiftAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi ->; +export { openshiftAuthApiRef }; // @public @deprecated export type OptionalParams< @@ -641,12 +483,7 @@ export type PathParams = { [name in ParamNames]: string; }; -// @public -export type PendingOAuthRequest = { - provider: AuthProviderInfo; - reject(): void; - trigger(): Promise; -}; +export { PendingOAuthRequest }; // @public export type PluginConfig< @@ -665,17 +502,9 @@ export type PluginFeatureFlagConfig = { name: string; }; -// @public -export type ProfileInfo = { - email?: string; - displayName?: string; - picture?: string; -}; +export { ProfileInfo }; -// @public -export type ProfileInfoApi = { - getProfile(options?: AuthRequestOptions): Promise; -}; +export { ProfileInfoApi }; // @public export type RouteFunc = ( @@ -686,52 +515,24 @@ export type RouteFunc = ( export type RouteRef = { $$routeRefType: 'absolute'; params: ParamKeys; + readonly $$type: '@backstage/RouteRef'; + readonly T: Params; }; -// @public -export type SessionApi = { - signIn(): Promise; - signOut(): Promise; - sessionState$(): Observable; -}; +export { SessionApi }; -// @public -export enum SessionState { - SignedIn = 'SignedIn', - SignedOut = 'SignedOut', -} +export { SessionState }; // @public export type SignInPageProps = PropsWithChildren<{ onSignInSuccess(identityApi: IdentityApi_2): void; }>; -// @public -export interface StorageApi { - forBucket(name: string): StorageApi; - observe$( - key: string, - ): Observable>; - remove(key: string): Promise; - set(key: string, data: T): Promise; - snapshot(key: string): StorageValueSnapshot; -} +export { StorageApi }; -// @public -export const storageApiRef: ApiRef; +export { storageApiRef }; -// @public -export type StorageValueSnapshot = - | { - key: string; - presence: 'unknown' | 'absent'; - value?: undefined; - } - | { - key: string; - presence: 'present'; - value: TValue; - }; +export { StorageValueSnapshot }; // @public export type SubRouteRef = { @@ -739,21 +540,18 @@ export type SubRouteRef = { parent: RouteRef; path: string; params: ParamKeys; + readonly $$type: '@backstage/SubRouteRef'; + readonly T: Params; }; -// @public -export type TypesToApiRefs = { - [key in keyof T]: ApiRef; -}; +export { TypesToApiRefs }; // @public export function useAnalytics(): AnalyticsTracker; -// @public -export function useApi(apiRef: ApiRef): T; +export { useApi }; -// @public -export function useApiHolder(): ApiHolder; +export { useApiHolder }; // @public export const useApp: () => AppContext; @@ -780,22 +578,7 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; -// @public -export const vmwareCloudAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi ->; +export { vmwareCloudAuthApiRef }; -// @public -export function withApis( - apis: TypesToApiRefs, -): ( - WrappedComponent: ComponentType, -) => { - (props: PropsWithChildren>): JSX_2.Element; - displayName: string; -}; +export { withApis }; ``` diff --git a/packages/core-plugin-api/src/alpha.ts b/packages/core-plugin-api/src/alpha.ts index ebcbcf34c0..f823b909b1 100644 --- a/packages/core-plugin-api/src/alpha.ts +++ b/packages/core-plugin-api/src/alpha.ts @@ -14,5 +14,26 @@ * limitations under the License. */ -export * from './translation'; -export * from './apis/alpha'; +// Translation exports +export { + type TranslationMessages, + type TranslationMessagesOptions, + createTranslationMessages, + type TranslationResource, + type TranslationResourceOptions, + createTranslationResource, + type TranslationRef, + type TranslationRefOptions, + createTranslationRef, + useTranslationRef, +} from '@backstage/frontend-plugin-api'; + +// API definition exports +export { + appLanguageApiRef, + type AppLanguageApi, + translationApiRef, + type TranslationApi, + type TranslationFunction, + type TranslationSnapshot, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx index 037e585430..45483ecb83 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,9 +16,12 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { useApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; -jest.mock('../apis'); +jest.mock('@backstage/frontend-plugin-api', () => ({ + ...jest.requireActual('@backstage/frontend-plugin-api'), + useApi: jest.fn(), +})); const mocked = (f: Function) => f as jest.Mock; diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.tsx index 50cec8f790..6297f81aa2 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.tsx @@ -15,12 +15,8 @@ */ import { useAnalyticsContext } from './AnalyticsContext'; -import { - analyticsApiRef, - AnalyticsTracker, - AnalyticsApi, - useApi, -} from '../apis'; +import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; +import { useApi } from '@backstage/frontend-plugin-api'; import { useRef } from 'react'; import { Tracker } from './Tracker'; diff --git a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts index afdcb6a617..a451195476 100644 --- a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,43 +14,8 @@ * limitations under the License. */ -import { createApiRef, ApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Message handled by the {@link AlertApi}. - * - * @public - */ -export type AlertMessage = { - message: string; - // Severity will default to success since that is what material ui defaults the value to. - severity?: 'success' | 'info' | 'warning' | 'error'; - display?: 'permanent' | 'transient'; -}; - -/** - * The alert API is used to report alerts to the app, and display them to the user. - * - * @public - */ -export type AlertApi = { - /** - * Post an alert for handling by the application. - */ - post(alert: AlertMessage): void; - - /** - * Observe alerts posted by other parts of the application. - */ - alert$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link AlertApi}. - * - * @public - */ -export const alertApiRef: ApiRef = createApiRef({ - id: 'core.alert', -}); +export { + type AlertApi, + type AlertMessage, + alertApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts index ef190d6c21..c6f4eadd05 100644 --- a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -122,7 +122,7 @@ export type AnalyticsApi = { }; /** - * The {@link ApiRef} of {@link AnalyticsApi}. + * The `ApiRef` of {@link AnalyticsApi}. * * @public */ diff --git a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts index e771fad597..2ad399d301 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,74 +14,8 @@ * limitations under the License. */ -import { ReactNode } from 'react'; -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Describes a theme provided by the app. - * - * @public - */ -export type AppTheme = { - /** - * ID used to remember theme selections. - */ - id: string; - - /** - * Title of the theme - */ - title: string; - - /** - * Theme variant - */ - variant: 'light' | 'dark'; - - /** - * An Icon for the theme mode setting. - */ - icon?: React.ReactElement; - - Provider(props: { children: ReactNode }): JSX.Element | null; -}; - -/** - * The AppThemeApi gives access to the current app theme, and allows switching - * to other options that have been registered as a part of the App. - * - * @public - */ -export type AppThemeApi = { - /** - * Get a list of available themes. - */ - getInstalledThemes(): AppTheme[]; - - /** - * Observe the currently selected theme. A value of undefined means no specific theme has been selected. - */ - activeThemeId$(): Observable; - - /** - * Get the current theme ID. Returns undefined if no specific theme is selected. - */ - getActiveThemeId(): string | undefined; - - /** - * Set a specific theme to use in the app, overriding the default theme selection. - * - * Clear the selection by passing in undefined. - */ - setActiveThemeId(themeId?: string): void; -}; - -/** - * The {@link ApiRef} of {@link AppThemeApi}. - * - * @public - */ -export const appThemeApiRef: ApiRef = createApiRef({ - id: 'core.apptheme', -}); +export { + type AppTheme, + type AppThemeApi, + appThemeApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts index d3bada9e46..e5e8b4559c 100644 --- a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,22 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Config } from '@backstage/config'; -/** - * The Config API is used to provide a mechanism to access the - * runtime configuration of the system. - * - * @public - */ -export type ConfigApi = Config; - -/** - * The {@link ApiRef} of {@link ConfigApi}. - * - * @public - */ -export const configApiRef: ApiRef = createApiRef({ - id: 'core.config', -}); +export { type ConfigApi, configApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts index d23fe3db6c..20c8639340 100644 --- a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,43 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -/** - * The discovery API is used to provide a mechanism for plugins to - * discover the endpoint to use to talk to their backend counterpart. - * - * @remarks - * - * The purpose of the discovery API is to allow for many different deployment - * setups and routing methods through a central configuration, instead - * of letting each individual plugin manage that configuration. - * - * Implementations of the discovery API can be a simple as a URL pattern - * using the pluginId, but could also have overrides for individual plugins, - * or query a separate discovery service. - * - * @public - */ -export type DiscoveryApi = { - /** - * Returns the HTTP base backend URL for a given plugin, without a trailing slash. - * - * This method must always be called just before making a request, as opposed to - * fetching the URL when constructing an API client. That is to ensure that more - * flexible routing patterns can be supported. - * - * For example, asking for the URL for `auth` may return something - * like `https://backstage.example.com/api/auth` - */ - getBaseUrl(pluginId: string): Promise; -}; - -/** - * The {@link ApiRef} of {@link DiscoveryApi}. - * - * @public - */ -export const discoveryApiRef: ApiRef = createApiRef({ - id: 'core.discovery', -}); +export { + type DiscoveryApi, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts index 9c73d94cac..8f3f9632eb 100644 --- a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,78 +14,9 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { Observable } from '@backstage/types'; - -/** - * Mirrors the JavaScript Error class, for the purpose of - * providing documentation and optional fields. - * - * @public - */ -export type ErrorApiError = { - name: string; - message: string; - stack?: string; -}; - -/** - * Provides additional information about an error that was posted to the application. - * - * @public - */ -export type ErrorApiErrorContext = { - /** - * If set to true, this error should not be displayed to the user. - * - * Hidden errors are typically not displayed in the UI, but the ErrorApi - * implementation may still report them to error tracking services - * or other utilities that care about all errors. - * - * @defaultValue false - */ - hidden?: boolean; -}; - -/** - * The error API is used to report errors to the app, and display them to the user. - * - * @remarks - * - * Plugins can use this API as a method of displaying errors to the user, but also - * to report errors for collection by error reporting services. - * - * If an error can be displayed inline, e.g. as feedback in a form, that should be - * preferred over relying on this API to display the error. The main use of this API - * for displaying errors should be for asynchronous errors, such as a failing background process. - * - * Even if an error is displayed inline, it should still be reported through this API - * if it would be useful to collect or log it for debugging purposes, but with - * the hidden flag set. For example, an error arising from form field validation - * should probably not be reported, while a failed REST call would be useful to report. - * - * @public - */ -export type ErrorApi = { - /** - * Post an error for handling by the application. - */ - post(error: ErrorApiError, context?: ErrorApiErrorContext): void; - - /** - * Observe errors posted by other parts of the application. - */ - error$(): Observable<{ - error: ErrorApiError; - context?: ErrorApiErrorContext; - }>; -}; - -/** - * The {@link ApiRef} of {@link ErrorApi}. - * - * @public - */ -export const errorApiRef: ApiRef = createApiRef({ - id: 'core.error', -}); +export { + type ErrorApiError, + type ErrorApiErrorContext, + type ErrorApi, + errorApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 66260afa32..9b192e784d 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -14,97 +14,10 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; - -/** - * Feature flag descriptor. - * - * @public - */ -export type FeatureFlag = { - name: string; - pluginId: string; - description?: string; -}; - -/** - * Enum representing the state of a feature flag (inactive/active). - * - * @public - */ -export enum FeatureFlagState { - /** - * Feature flag inactive (disabled). - */ - None = 0, - /** - * Feature flag active (enabled). - */ - Active = 1, -} - -/** - * Options to use when saving feature flags. - * - * @public - */ -export type FeatureFlagsSaveOptions = { - /** - * The new feature flag states to save. - */ - states: Record; - - /** - * Whether the saves states should be merged into the existing ones, or replace them. - * - * Defaults to false. - */ - merge?: boolean; -}; - -/** - * The feature flags API is used to toggle functionality to users across plugins and Backstage. - * - * @remarks - * - * Plugins can use this API to register feature flags that they have available - * for users to enable/disable, and this API will centralize the current user's - * state of which feature flags they would like to enable. - * - * This is ideal for Backstage plugins, as well as your own App, to trial incomplete - * or unstable upcoming features. Although there will be a common interface for users - * to enable and disable feature flags, this API acts as another way to enable/disable. - * - * @public - */ -export interface FeatureFlagsApi { - /** - * Registers a new feature flag. Once a feature flag has been registered it - * can be toggled by users, and read back to enable or disable features. - */ - registerFlag(flag: FeatureFlag): void; - - /** - * Get a list of all registered flags. - */ - getRegisteredFlags(): FeatureFlag[]; - - /** - * Whether the feature flag with the given name is currently activated for the user. - */ - isActive(name: string): boolean; - - /** - * Save the user's choice of feature flag states. - */ - save(options: FeatureFlagsSaveOptions): void; -} - -/** - * The {@link ApiRef} of {@link FeatureFlagsApi}. - * - * @public - */ -export const featureFlagsApiRef: ApiRef = createApiRef({ - id: 'core.featureflags', -}); +export { + type FeatureFlag, + type FeatureFlagsApi, + type FeatureFlagsSaveOptions, + FeatureFlagState, + featureFlagsApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts index aba0e53bb7..7fdf949e58 100644 --- a/packages/core-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,38 +14,4 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; - -/** - * A wrapper for the fetch API, that has additional behaviors such as the - * ability to automatically inject auth information where necessary. - * - * @public - */ -export type FetchApi = { - /** - * The `fetch` implementation. - */ - fetch: typeof fetch; -}; - -/** - * The {@link ApiRef} of {@link FetchApi}. - * - * @remarks - * - * This is a wrapper for the fetch API, that has additional behaviors such as - * the ability to automatically inject auth information where necessary. - * - * Note that the default behavior of this API (unless overridden by your org), - * is to require that the user is already signed in so that it has auth - * information to inject. Therefore, using the default implementation of this - * utility API e.g. on the `SignInPage` or similar, would cause issues. In - * special circumstances like those, you can use the regular system `fetch` - * instead. - * - * @public - */ -export const fetchApiRef: ApiRef = createApiRef({ - id: 'core.fetch', -}); +export { type FetchApi, fetchApiRef } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index 1b127a1971..0648f96059 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,44 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { BackstageUserIdentity, ProfileInfo } from './auth'; -/** - * The Identity API used to identify and get information about the signed in user. - * - * @public - */ -export type IdentityApi = { - /** - * The profile of the signed in user. - */ - getProfileInfo(): Promise; - - /** - * User identity information within Backstage. - */ - getBackstageIdentity(): Promise; - - /** - * Provides credentials in the form of a token which proves the identity of the signed in user. - * - * The token will be undefined if the signed in user does not have a verified - * identity, such as a demo user or mocked user for e2e tests. - */ - getCredentials(): Promise<{ token?: string }>; - - /** - * Sign out the current user - */ - signOut(): Promise; -}; - -/** - * The {@link ApiRef} of {@link IdentityApi}. - * - * @public - */ -export const identityApiRef: ApiRef = createApiRef({ - id: 'core.identity', -}); +export { + type IdentityApi, + identityApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts index 75bf3a3864..c217b77fb5 100644 --- a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,118 +14,10 @@ * limitations under the License. */ -import { Observable } from '@backstage/types'; -import { ApiRef, createApiRef } from '../system'; -import { AuthProviderInfo } from './auth'; - -/** - * Describes how to handle auth requests. Both how to show them to the user, and what to do when - * the user accesses the auth request. - * - * @public - */ -export type OAuthRequesterOptions = { - /** - * Information about the auth provider, which will be forwarded to auth requests. - */ - provider: AuthProviderInfo; - - /** - * Implementation of the auth flow, which will be called synchronously when - * trigger() is called on an auth requests. - */ - onAuthRequest(scopes: Set): Promise; -}; - -/** - * Function used to trigger new auth requests for a set of scopes. - * - * @remarks - * - * The returned promise will resolve to the same value returned by the onAuthRequest in the - * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. - * - * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the - * union of all requested scopes. - * - * @public - */ -export type OAuthRequester = ( - scopes: Set, -) => Promise; - -/** - * An pending auth request for a single auth provider. The request will remain in this pending - * state until either reject() or trigger() is called. - * - * @remarks - * - * Any new requests for the same provider are merged into the existing pending request, meaning - * there will only ever be a single pending request for a given provider. - * - * @public - */ -export type PendingOAuthRequest = { - /** - * Information about the auth provider, as given in the AuthRequesterOptions - */ - provider: AuthProviderInfo; - - /** - * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". - */ - reject(): void; - - /** - * Trigger the auth request to continue the auth flow, by for example showing a popup. - * - * Synchronously calls onAuthRequest with all scope currently in the request. - */ - trigger(): Promise; -}; - -/** - * Provides helpers for implemented OAuth login flows within Backstage. - * - * @public - */ -export type OAuthRequestApi = { - /** - * A utility for showing login popups or similar things, and merging together multiple requests for - * different scopes into one request that includes all scopes. - * - * The passed in options provide information about the login provider, and how to handle auth requests. - * - * The returned AuthRequester function is used to request login with new scopes. These requests - * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests - * triggers an auth flow. - * - * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. - */ - createAuthRequester( - options: OAuthRequesterOptions, - ): OAuthRequester; - - /** - * Observers pending auth requests. The returned observable will emit all - * current active auth request, at most one for each created auth requester. - * - * Each request has its own info about the login provider, forwarded from the auth requester options. - * - * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. - * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". - * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the onAuthRequest call. - */ - authRequest$(): Observable; -}; - -/** - * The {@link ApiRef} of {@link OAuthRequestApi}. - * - * @public - */ -export const oauthRequestApiRef: ApiRef = createApiRef({ - id: 'core.oauthrequest', -}); +export { + type OAuthRequesterOptions, + type OAuthRequester, + type PendingOAuthRequest, + type OAuthRequestApi, + oauthRequestApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 1506e5f143..4f3af7b611 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,97 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '../system'; -import { JsonValue, Observable } from '@backstage/types'; - -/** - * A snapshot in time of the current known value of a storage key. - * - * @public - */ -export type StorageValueSnapshot = - | { - key: string; - presence: 'unknown' | 'absent'; - value?: undefined; - } - | { - key: string; - presence: 'present'; - value: TValue; - }; - -/** - * Provides a key-value persistence API. - * - * @public - */ -export interface StorageApi { - /** - * Create a bucket to store data in. - * - * @param name - Namespace for the storage to be stored under, - * will inherit previous namespaces too - */ - forBucket(name: string): StorageApi; - - /** - * Remove persistent data. - * - * @param key - Unique key associated with the data. - */ - remove(key: string): Promise; - - /** - * Save persistent data, and emit messages to anyone that is using - * {@link StorageApi.observe$} for this key. - * - * @param key - Unique key associated with the data. - * @param data - The data to be stored under the key. - */ - set(key: string, data: T): Promise; - - /** - * Observe the value over time for a particular key in the current bucket. - * - * @remarks - * - * The observable will only emit values when the value changes in the underlying - * storage, although multiple values with the same shape may be emitted in a row. - * - * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is - * `'unknown'`, then you are guaranteed to receive a snapshot with a known - * presence, as long as you observe the key within the same tick. - * - * Since the emitted values are shared across all subscribers, it is important - * not to mutate the returned values. The values may be frozen as a precaution. - * - * @param key - Unique key associated with the data - */ - observe$( - key: string, - ): Observable>; - - /** - * Returns an immediate snapshot value for the given key, if possible. - * - * @remarks - * - * Combine with {@link StorageApi.observe$} to get notified of value changes. - * - * Note that this method is synchronous, and some underlying storages may be - * unable to retrieve a value using this method - the result may or may not - * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} - * to be sure to receive an actual value eventually. - */ - snapshot(key: string): StorageValueSnapshot; -} - -/** - * The {@link ApiRef} of {@link StorageApi}. - * - * @public - */ -export const storageApiRef: ApiRef = createApiRef({ - id: 'core.storage', -}); +export { + type StorageValueSnapshot, + type StorageApi, + storageApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index f8686cb128..a60c84c635 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -13,481 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -import { ApiRef, createApiRef } from '../system'; -import { IconComponent } from '../../icons/types'; -import { Observable } from '@backstage/types'; - -/** - * This file contains declarations for common interfaces of auth-related APIs. - * The declarations should be used to signal which type of authentication and - * authorization methods each separate auth provider supports. - * - * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, - * would be declared as follows: - * - * const googleAuthApiRef = createApiRef({ ... }) - */ - -/** - * Information about the auth provider. - * - * @remarks - * - * This information is used both to connect the correct auth provider in the backend, as - * well as displaying the provider to the user. - * - * @public - */ -export type AuthProviderInfo = { - /** - * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. - */ - id: string; - - /** - * Title for the auth provider, for example "GitHub" - */ - title: string; - - /** - * Icon for the auth provider. - */ - icon: IconComponent; - - /** - * Optional user friendly messaage to display for the auth provider. - */ - message?: string; -}; - -/** - * An array of scopes, or a scope string formatted according to the - * auth provider, which is typically a space separated list. - * - * @remarks - * - * See the documentation for each auth provider for the list of scopes - * supported by each provider. - * - * @public - */ -export type OAuthScope = string | string[]; - -/** - * Configuration of an authentication request. - * - * @public - */ -export type AuthRequestOptions = { - /** - * If this is set to true, the user will not be prompted to log in, - * and an empty response will be returned if there is no existing session. - * - * This can be used to perform a check whether the user is logged in, or if you don't - * want to force a user to be logged in, but provide functionality if they already are. - * - * @defaultValue false - */ - optional?: boolean; - - /** - * If this is set to true, the request will bypass the regular oauth login modal - * and open the login popup directly. - * - * The method must be called synchronously from a user action for this to work in all browsers. - * - * @defaultValue false - */ - instantPopup?: boolean; -}; - -/** - * This API provides access to OAuth 2 credentials. It lets you request access tokens, - * which can be used to act on behalf of the user when talking to APIs. - * - * @public - */ -export type OAuthApi = { - /** - * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows - * you to make requests on behalf of the user, and the copes may grant you broader access, depending - * on the auth provider. - * - * Each auth provider has separate handling of scope, so you need to look at the documentation - * for each one to know what scope you need to request. - * - * This method is cheap and should be called each time an access token is used. Do not for example - * store the access token in React component state, as that could cause the token to expire. Instead - * fetch a new access token for each request. - * - * Be sure to include all required scopes when requesting an access token. When testing your implementation - * it is best to log out the Backstage session and then visit your plugin page directly, as - * you might already have some required scopes in your existing session. Not requesting the correct - * scopes can lead to 403 or other authorization errors, which can be tricky to debug. - * - * If the user has not yet granted access to the provider and the set of requested scopes, the user - * will be prompted to log in. The returned promise will not resolve until the user has - * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. - */ - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * This API provides access to OpenID Connect credentials. It lets you request ID tokens, - * which can be passed to backend services to prove the user's identity. - * - * @public - */ -export type OpenIdConnectApi = { - /** - * Requests an OpenID Connect ID Token. - * - * This method is cheap and should be called each time an ID token is used. Do not for example - * store the id token in React component state, as that could cause the token to expire. Instead - * fetch a new id token for each request. - * - * If the user has not yet logged in to Google inside Backstage, the user will be prompted - * to log in. The returned promise will not resolve until the user has successfully logged in. - * The returned promise can be rejected, but only if the user rejects the login request. - */ - getIdToken(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to profile information of the user from an auth provider. - * - * @public - */ -export type ProfileInfoApi = { - /** - * Get profile information for the user as supplied by this auth provider. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getProfile(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to the user's identity within Backstage. - * - * @remarks - * - * An auth provider that implements this interface can be used to sign-in to backstage. It is - * not intended to be used directly from a plugin, but instead serves as a connection between - * this authentication method and the app's {@link IdentityApi} - * - * @public - */ -export type BackstageIdentityApi = { - /** - * Get the user's identity within Backstage. This should normally not be called directly, - * use the {@link IdentityApi} instead. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. - */ - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * User identity information within Backstage. - * - * @public - */ -export type BackstageUserIdentity = { - /** - * The type of identity that this structure represents. In the frontend app - * this will currently always be 'user'. - */ - type: 'user'; - - /** - * The entityRef of the user in the catalog. - * For example User:default/sandra - */ - userEntityRef: string; - - /** - * The user and group entities that the user claims ownership through - */ - ownershipEntityRefs: string[]; -}; - -/** - * Token and Identity response, with the users claims in the Identity. - * - * @public - */ -export type BackstageIdentityResponse = { - /** - * The token used to authenticate the user within Backstage. - */ - token: string; - - /** - * The time at which the token expires. If not set, it can be assumed that the token does not expire. - */ - expiresAt?: Date; - - /** - * Identity information derived from the token. - */ - identity: BackstageUserIdentity; -}; - -/** - * Profile information of the user. - * - * @public - */ -export type ProfileInfo = { - /** - * Email ID. - */ - email?: string; - - /** - * Display name that can be presented to the user. - */ - displayName?: string; - - /** - * URL to an avatar image of the user. - */ - picture?: string; -}; - -/** - * Session state values passed to subscribers of the SessionApi. - * - * @public - */ -export enum SessionState { - /** - * User signed in. - */ - SignedIn = 'SignedIn', - /** - * User not signed in. - */ - SignedOut = 'SignedOut', -} - -/** - * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. - * - * @public - */ -export type SessionApi = { - /** - * Sign in with a minimum set of permissions. - */ - signIn(): Promise; - - /** - * Sign out from the current session. This will reload the page. - */ - signOut(): Promise; - - /** - * Observe the current state of the auth session. Emits the current state on subscription. - */ - sessionState$(): Observable; -}; - -/** - * Provides authentication towards Google APIs and identities. - * - * @public - * @remarks - * - * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. - * - * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, - * email and expiration information. Do not rely on any other fields, as they might not be present. - */ -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.google', -}); - -/** - * Provides authentication towards GitHub APIs. - * - * @public - * @remarks - * - * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} - * for a full list of supported scopes. - */ -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.github', -}); - -/** - * Provides authentication towards Okta APIs. - * - * @public - * @remarks - * - * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} - * for a full list of supported scopes. - */ -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.okta', -}); - -/** - * Provides authentication towards GitLab APIs. - * - * @public - * @remarks - * - * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} - * for a full list of supported scopes. - */ -export const gitlabAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.gitlab', -}); - -/** - * Provides authentication towards Microsoft APIs and identities. - * - * @public - * @remarks - * - * For more info and a full list of supported scopes, see: - * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} - * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} - */ -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.microsoft', -}); - -/** - * Provides authentication towards OneLogin APIs. - * - * @public - */ -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.onelogin', -}); - -/** - * Provides authentication towards Bitbucket APIs. - * - * @public - * @remarks - * - * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} - * for a full list of supported scopes. - */ -export const bitbucketAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket', -}); - -/** - * Provides authentication towards Bitbucket Server APIs. - * - * @public - * @remarks - * - * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} - * for a full list of supported scopes. - */ -export const bitbucketServerAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.bitbucket-server', -}); - -/** - * Provides authentication towards Atlassian APIs. - * - * @public - * @remarks - * - * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} - * for a full list of supported scopes. - */ -export const atlassianAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.atlassian', -}); - -/** - * Provides authentication towards VMware Cloud APIs and identities. - * - * @public - * @remarks - * - * For more info about VMware Cloud identity and access management: - * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} - */ -export const vmwareCloudAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.vmware-cloud', -}); - -/** - * Provides authentication towards OpenShift APIs and identities. - * - * @public - * @remarks - * - * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} - * on how to configure the OAuth clients and - * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} - * for available scopes. - */ -export const openshiftAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.openshift', -}); +export { + type AuthProviderInfo, + type OAuthScope, + type AuthRequestOptions, + type OAuthApi, + type OpenIdConnectApi, + type ProfileInfoApi, + type BackstageIdentityApi, + type BackstageUserIdentity, + type BackstageIdentityResponse, + type ProfileInfo, + SessionState, + type SessionApi, + googleAuthApiRef, + githubAuthApiRef, + oktaAuthApiRef, + gitlabAuthApiRef, + microsoftAuthApiRef, + oneloginAuthApiRef, + bitbucketAuthApiRef, + bitbucketServerAuthApiRef, + atlassianAuthApiRef, + vmwareCloudAuthApiRef, + openshiftAuthApiRef, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.ts b/packages/core-plugin-api/src/apis/system/ApiRef.ts index adedff9f73..ffd074b9a7 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.ts @@ -14,51 +14,5 @@ * limitations under the License. */ -import type { ApiRef } from './types'; - -/** - * API reference configuration - holds an ID of the referenced API. - * - * @public - */ -export type ApiRefConfig = { - id: string; -}; - -class ApiRefImpl implements ApiRef { - constructor(private readonly config: ApiRefConfig) { - const valid = config.id - .split('.') - .flatMap(part => part.split('-')) - .every(part => part.match(/^[a-z][a-z0-9]*$/)); - if (!valid) { - throw new Error( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, - ); - } - } - - get id(): string { - return this.config.id; - } - - // Utility for getting type of an api, using `typeof apiRef.T` - get T(): T { - throw new Error(`tried to read ApiRef.T of ${this}`); - } - - toString() { - return `apiRef{${this.config.id}}`; - } -} - -/** - * Creates a reference to an API. - * - * @param config - The descriptor of the API to reference. - * @returns An API reference. - * @public - */ -export function createApiRef(config: ApiRefConfig): ApiRef { - return new ApiRefImpl(config); -} +export { createApiRef } from '@backstage/frontend-plugin-api'; +export type { ApiRefConfig } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/helpers.ts b/packages/core-plugin-api/src/apis/system/helpers.ts index eaffed3003..07acaa6a53 100644 --- a/packages/core-plugin-api/src/apis/system/helpers.ts +++ b/packages/core-plugin-api/src/apis/system/helpers.ts @@ -14,61 +14,4 @@ * limitations under the License. */ -import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; - -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * This function doesn't actually do anything, it's only used to infer types. - * - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->(factory: ApiFactory): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @param api - Ref of the API that will be produced by the factory. - * @param instance - Implementation of the API to use. - * @public - */ -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; -/** - * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed - * to another function. - * - * @remarks - * - * Creates factory from {@link ApiRef} or returns the factory itself if provided. - * - * @param factory - Existing factory or {@link ApiRef}. - * @param instance - The instance to be returned by the factory. - * @public - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->( - factory: ApiFactory | ApiRef, - instance?: Impl, -): ApiFactory { - if ('id' in factory) { - return { - api: factory, - deps: {} as TypesToApiRefs, - factory: () => instance!, - }; - } - return factory; -} +export { createApiFactory } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/types.ts b/packages/core-plugin-api/src/apis/system/types.ts index 96614c320c..3b4ba449d3 100644 --- a/packages/core-plugin-api/src/apis/system/types.ts +++ b/packages/core-plugin-api/src/apis/system/types.ts @@ -14,61 +14,11 @@ * limitations under the License. */ -/** - * API reference. - * - * @public - */ -export type ApiRef = { - id: string; - T: T; -}; - -/** - * Catch-all {@link ApiRef} type. - * - * @public - */ -export type AnyApiRef = ApiRef; - -/** - * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. - * - * @public - */ -export type TypesToApiRefs = { [key in keyof T]: ApiRef }; - -/** - * Provides lookup of APIs through their {@link ApiRef}s. - * - * @public - */ -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; - -/** - * Describes type returning API implementations. - * - * @public - */ -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; - -/** - * Catch-all {@link ApiFactory} type. - * - * @public - */ -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { [key in string]: unknown } ->; +export type { + ApiRef, + AnyApiRef, + TypesToApiRefs, + ApiHolder, + ApiFactory, + AnyApiFactory, +} from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/apis/system/useApi.tsx b/packages/core-plugin-api/src/apis/system/useApi.tsx index 30e39033f7..a2caec8aea 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.tsx @@ -14,81 +14,4 @@ * limitations under the License. */ -import { ComponentType, PropsWithChildren } from 'react'; -import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; -import { useVersionedContext } from '@backstage/version-bridge'; -import { NotImplementedError } from '@backstage/errors'; - -/** - * React hook for retrieving {@link ApiHolder}, an API catalog. - * - * @public - */ -export function useApiHolder(): ApiHolder { - const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); - if (!versionedHolder) { - throw new NotImplementedError('API context is not available'); - } - - const apiHolder = versionedHolder.atVersion(1); - if (!apiHolder) { - throw new NotImplementedError('ApiContext v1 not available'); - } - return apiHolder; -} - -/** - * React hook for retrieving APIs. - * - * @param apiRef - Reference of the API to use. - * @public - */ -export function useApi(apiRef: ApiRef): T { - const apiHolder = useApiHolder(); - - const api = apiHolder.get(apiRef); - if (!api) { - throw new NotImplementedError(`No implementation available for ${apiRef}`); - } - return api; -} - -/** - * Wrapper for giving component an API context. - * - * @param apis - APIs for the context. - * @public - */ -export function withApis(apis: TypesToApiRefs) { - return function withApisWrapper( - WrappedComponent: ComponentType, - ) { - const Hoc = (props: PropsWithChildren>) => { - const apiHolder = useApiHolder(); - - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = apiHolder.get(ref); - if (!api) { - throw new NotImplementedError( - `No implementation available for ${ref}`, - ); - } - impls[key] = api; - } - } - - return ; - }; - const displayName = - WrappedComponent.displayName || WrappedComponent.name || 'Component'; - - Hoc.displayName = `withApis(${displayName})`; - - return Hoc; - }; -} +export { useApiHolder, useApi, withApis } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/app/useApp.test.tsx b/packages/core-plugin-api/src/app/useApp.test.tsx index 34c956138e..2cee7f6f3c 100644 --- a/packages/core-plugin-api/src/app/useApp.test.tsx +++ b/packages/core-plugin-api/src/app/useApp.test.tsx @@ -15,20 +15,129 @@ */ import { renderHook } from '@testing-library/react'; +import { PropsWithChildren } from 'react'; import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { + appTreeApiRef, + iconsApiRef, + AppTreeApi, + IconsApi, + AppTree, + AppNode, +} from '@backstage/frontend-plugin-api'; import { useApp } from './useApp'; +import { TestApiProvider, withLogCollector } from '@backstage/test-utils'; -describe('v1 consumer', () => { - const context = createVersionedContextForTesting('app-context'); +describe('useApp', () => { + describe('old system', () => { + const context = createVersionedContextForTesting('app-context'); - afterEach(() => { - context.reset(); + afterEach(() => { + context.reset(); + }); + + it('should provide an app context', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + {children} + ); + context.set({ 1: 'context-value' }); + + const renderedHook = renderHook(() => useApp(), { wrapper }); + expect(renderedHook.result.current).toBe('context-value'); + }); }); - it('should provide an app context', () => { - context.set({ 1: 'context-value' }); + describe('new system', () => { + const mockIcon = () => null; + const mockIconsApi: IconsApi = { + getIcon: jest.fn((key: string) => + key === 'test-icon' ? mockIcon : undefined, + ), + listIconKeys: jest.fn(() => ['test-icon']), + }; - const renderedHook = renderHook(() => useApp()); - expect(renderedHook.result.current).toBe('context-value'); + const mockPlugin = { + id: 'test-plugin', + }; + + const mockAppNode: AppNode = { + spec: { + id: 'test-node', + attachTo: { id: 'root', input: 'children' }, + extension: {} as any, + disabled: false, + plugin: mockPlugin as any, + }, + edges: { + attachments: new Map(), + }, + }; + + const mockAppTree: AppTree = { + root: mockAppNode, + nodes: new Map([['test-node', mockAppNode]]), + orphans: [], + }; + + const mockAppTreeApi: AppTreeApi = { + getTree: jest.fn(() => ({ tree: mockAppTree })), + getNodesByRoutePath: jest.fn(() => ({ nodes: [] })), + }; + + it('should provide an app context from new app system', () => { + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useApp(), { wrapper }); + + const appContext = renderedHook.result.current; + expect(appContext).toBeDefined(); + expect(appContext.getPlugins()).toHaveLength(1); + expect(appContext.getPlugins()[0].getId()).toBe('test-plugin'); + expect(appContext.getSystemIcon('test-icon')).toBe(mockIcon); + expect(appContext.getSystemIcons()).toEqual({ 'test-icon': mockIcon }); + expect(appContext.getComponents().Progress).toBeDefined(); + expect(appContext.getComponents().NotFoundErrorPage).toBeDefined(); + }); + + it('should error on missing appTreeApi or iconsApi', () => { + withLogCollector(['error'], () => { + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + {children} + ), + }), + ).toThrow('App context is not available'); + + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }), + ).toThrow('App context is not available'); + + expect(() => + renderHook(() => useApp(), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ), + }), + ).toThrow('App context is not available'); + }); + }); }); }); diff --git a/packages/core-plugin-api/src/app/useApp.tsx b/packages/core-plugin-api/src/app/useApp.tsx index 05723ee387..fde807c317 100644 --- a/packages/core-plugin-api/src/app/useApp.tsx +++ b/packages/core-plugin-api/src/app/useApp.tsx @@ -14,18 +14,159 @@ * limitations under the License. */ +import { useMemo } from 'react'; import { useVersionedContext } from '@backstage/version-bridge'; +import { + appTreeApiRef, + iconsApiRef, + useApiHolder, + ErrorDisplay, + NotFoundErrorPage, + Progress, + createFrontendPlugin, + FrontendPlugin, + ApiHolder, +} from '@backstage/frontend-plugin-api'; +import { + AppComponents, + IconComponent, + BackstagePlugin, +} from '@backstage/core-plugin-api'; +import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; import { AppContext as AppContextV1 } from './types'; +const legacyPluginStore = getOrCreateGlobalSingleton( + 'legacy-plugin-compatibility-store', + () => new WeakMap(), +); + +function toLegacyPlugin(plugin: FrontendPlugin): BackstagePlugin { + let legacy = legacyPluginStore.get(plugin); + if (legacy) { + return legacy; + } + + const errorMsg = 'Not implemented in legacy plugin compatibility layer'; + const notImplemented = () => { + throw new Error(errorMsg); + }; + + legacy = { + getId(): string { + return plugin.id; + }, + get routes() { + return {}; + }, + get externalRoutes() { + return {}; + }, + getApis: notImplemented, + getFeatureFlags: notImplemented, + provide: notImplemented, + }; + + legacyPluginStore.set(plugin, legacy); + return legacy; +} + +function toNewPlugin(plugin: BackstagePlugin): FrontendPlugin { + return createFrontendPlugin({ + pluginId: plugin.getId(), + }); +} + +function useOptionalApiHolder(): ApiHolder | undefined { + try { + return useApiHolder(); + } catch { + return undefined; + } +} + /** * React hook providing {@link AppContext}. * * @public */ export const useApp = (): AppContextV1 => { + const apiHolder = useOptionalApiHolder(); + const appTreeApi = apiHolder?.get(appTreeApiRef); + const iconsApi = apiHolder?.get(iconsApiRef); const versionedContext = useVersionedContext<{ 1: AppContextV1 }>( 'app-context', ); + + const newAppContext = useMemo(() => { + if (!appTreeApi) { + return undefined; + } + + if (!iconsApi) { + return undefined; + } + + const { tree } = appTreeApi.getTree(); + + let gatheredPlugins: BackstagePlugin[] | undefined = undefined; + + const ErrorBoundaryFallbackWrapper: AppComponents['ErrorBoundaryFallback'] = + ({ plugin, ...rest }) => ( + + ); + + return { + getPlugins(): BackstagePlugin[] { + if (gatheredPlugins) { + return gatheredPlugins; + } + + const pluginSet = new Set(); + for (const node of tree.nodes.values()) { + const plugin = node.spec.plugin; + if (plugin) { + pluginSet.add(toLegacyPlugin(plugin)); + } + } + gatheredPlugins = Array.from(pluginSet); + + return gatheredPlugins; + }, + + getSystemIcon(key: string): IconComponent | undefined { + return iconsApi.getIcon(key); + }, + + getSystemIcons(): Record { + return Object.fromEntries( + iconsApi.listIconKeys().map(key => [key, iconsApi.getIcon(key)!]), + ); + }, + + getComponents(): AppComponents { + return { + NotFoundErrorPage: NotFoundErrorPage, + BootErrorPage() { + throw new Error( + 'The BootErrorPage app component should not be accessed by plugins', + ); + }, + Progress: Progress, + Router() { + throw new Error( + 'The Router app component should not be accessed by plugins', + ); + }, + ErrorBoundaryFallback: ErrorBoundaryFallbackWrapper, + }; + }, + }; + }, [appTreeApi, iconsApi]); + + if (newAppContext) { + return newAppContext; + } + if (!versionedContext) { throw new Error('App context is not available'); } diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts index 2b00e1456f..2e64828584 100644 --- a/packages/core-plugin-api/src/icons/types.ts +++ b/packages/core-plugin-api/src/icons/types.ts @@ -14,24 +14,4 @@ * limitations under the License. */ -import { ComponentType } from 'react'; - -/** - * IconComponent is the common icon type used throughout Backstage when - * working with and rendering generic icons, including the app system icons. - * - * @remarks - * - * The type is based on SvgIcon from Material UI, but we do not want the plugin-api - * package to have a dependency on Material UI, nor do we want the props to be as broad - * as the SvgIconProps interface. - * - * If you have the need to forward additional props from SvgIconProps, you can - * open an issue or submit a PR to the main Backstage repo. When doing so please - * also describe your use-case and reasoning of the addition. - * - * @public - */ -export type IconComponent = ComponentType<{ - fontSize?: 'medium' | 'large' | 'small' | 'inherit'; -}>; +export type { IconComponent } from '@backstage/frontend-plugin-api'; diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts index 05e7a23d5d..8c3a5f0802 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts @@ -16,6 +16,7 @@ import { AnyParams, ExternalRouteRef } from './types'; import { createExternalRouteRef } from './ExternalRouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; describe('ExternalRouteRef', () => { it('should be created', () => { @@ -24,7 +25,9 @@ describe('ExternalRouteRef', () => { }); expect(routeRef.params).toEqual([]); expect(routeRef.optional).toBe(false); - expect(String(routeRef)).toBe('routeRef{type=external,id=my-route-ref}'); + expect(String(routeRef)).toMatch( + /^routeRef\{type=external,id=my-route-ref\}$/, + ); }); it('should be created as optional', () => { @@ -109,4 +112,48 @@ describe('ExternalRouteRef', () => { // To avoid complains about missing expectations and unused vars expect([_1, _2, _3, _4, _5, _6].join('')).toEqual(expect.any(String)); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve(createExternalRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', optional: true }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', optional: false }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'] }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'], optional: true }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createExternalRouteRef({ id: '1', params: ['x'], optional: false }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts index 16e2f9bc72..5d33aa7a67 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts @@ -52,12 +52,45 @@ export class ExternalRouteRefImpl< } toString() { + if (this.#nfsId) { + return `externalRouteRef{id=${this.#nfsId},legacyId=${this.id}}`; + } return `routeRef{type=external,id=${this.id}}`; } getDefaultTarget() { return this.defaultTarget; } + + // NFS implementation below + readonly $$type = '@backstage/ExternalRouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + + #nfsId: string | undefined = undefined; + + getParams(): string[] { + return this.params as string[]; + } + getDescription(): string { + if (this.#nfsId) { + return this.#nfsId; + } + return this.id; + } + setId(newId: string) { + if (!newId) { + throw new Error(`ExternalRouteRef id must be a non-empty string`); + } + if (this.#nfsId && this.#nfsId !== newId) { + throw new Error( + `ExternalRouteRef was referenced twice as both '${ + this.#nfsId + }' and '${newId}'`, + ); + } + this.#nfsId = newId; + } } /** @@ -106,5 +139,5 @@ export function createExternalRouteRef< (options.params ?? []) as ParamKeys>, Boolean(options.optional) as Optional, options?.defaultTarget, - ); + ) as ExternalRouteRef, Optional>; } diff --git a/packages/core-plugin-api/src/routing/RouteRef.test.ts b/packages/core-plugin-api/src/routing/RouteRef.test.ts index 489900d892..29ab228b9f 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.test.ts @@ -16,6 +16,7 @@ import { AnyParams, RouteRef, ParamKeys } from './types'; import { createRouteRef } from './RouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; describe('RouteRef', () => { it('should be created', () => { @@ -23,7 +24,9 @@ describe('RouteRef', () => { id: 'my-route-ref', }); expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=my-route-ref}'); + expect(String(routeRef)).toMatch( + /^routeRef\{type=absolute,id=my-route-ref\}$/, + ); }); it('should be created with params', () => { @@ -95,4 +98,37 @@ describe('RouteRef', () => { expect(true).toBeDefined(); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1' })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1', params: [] })), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve(createRouteRef({ id: '1', params: ['x'] })), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createRouteRef({ id: '1', params: ['x', 'y'] }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts index d78f3ddb8d..c8465dafde 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.ts @@ -45,8 +45,40 @@ export class RouteRefImpl } toString() { + if (this.#nfsId) { + return `routeRef{id=${this.#nfsId},legacyId=${this.id}}`; + } return `routeRef{type=absolute,id=${this.id}}`; } + + // NFS implementation below + readonly $$type = '@backstage/RouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + readonly alias = undefined; + + #nfsId: string | undefined = undefined; + + getParams() { + return this.params; + } + getDescription() { + if (this.#nfsId) { + return this.#nfsId; + } + return this.id; + } + setId(newId: string) { + if (!newId) { + throw new Error(`RouteRef id must be a non-empty string`); + } + if (this.#nfsId && this.#nfsId !== newId) { + throw new Error( + `RouteRef was referenced twice as both '${this.#nfsId}' and '${newId}'`, + ); + } + this.#nfsId = newId; + } } /** @@ -72,5 +104,5 @@ export function createRouteRef< return new RouteRefImpl( config.id, (config.params ?? []) as ParamKeys>, - ); + ) as RouteRef>; } diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts index 1db429e178..ccec4c97d6 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts @@ -17,6 +17,7 @@ import { AnyParams, SubRouteRef } from './types'; import { createSubRouteRef } from './SubRouteRef'; import { createRouteRef } from './RouteRef'; +import { RouteResolutionApi, RouteFunc } from '@backstage/frontend-plugin-api'; const parent = createRouteRef({ id: 'parent' }); const parentX = createRouteRef({ id: 'parent-x', params: ['x'] }); @@ -31,7 +32,7 @@ describe('SubRouteRef', () => { expect(routeRef.path).toBe('/foo'); expect(routeRef.parent).toBe(parent); expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=sub,id=my-route-ref}'); + expect(String(routeRef)).toMatch(/^routeRef\{type=sub,id=my-route-ref\}$/); }); it('should be created with params', () => { @@ -125,4 +126,51 @@ describe('SubRouteRef', () => { // To avoid complains about missing expectations and unused vars expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); }); + + describe('with new frontend system', () => { + const routeResolutionApi = { resolve: jest.fn() } as RouteResolutionApi; + + function expectType(): ( + v: U, + ) => [T, U] extends [U, T] ? { ok(): void } : { invalid: U } { + return () => ({ ok() {} } as any); + } + + it('should resolve routes correctly', () => { + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/foo' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/foo' }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/:y' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/:y' }), + ), + ).ok(); + + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent, path: '/:y/:z' }), + ), + ).ok(); + expectType | undefined>()( + routeResolutionApi.resolve( + createSubRouteRef({ id: '1', parent: parentX, path: '/:y/:z' }), + ), + ).ok(); + + expect(1).toBe(1); + }); + }); }); diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index ed9ded41d4..8db4c1947b 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -56,6 +56,21 @@ export class SubRouteRefImpl toString() { return `routeRef{type=sub,id=${this.id}}`; } + + // NFS implementation below + readonly $$type = '@backstage/SubRouteRef'; + readonly version = 'v1'; + readonly T = undefined as any; + + getParams(): string[] { + return this.params as string[]; + } + getParent(): RouteRef { + return this.parent; + } + getDescription(): string { + return `at ${this.path} with parent ${this.parent}`; + } } /** @@ -155,7 +170,7 @@ export function createSubRouteRef< path, parent, params as ParamKeys>, - ) as SubRouteRef>>; + ); // But skip type checking of the return value itself, because the conditional // type checking of the parent parameter overlap is tricky to express. diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts index 4598574afb..b0a4d83918 100644 --- a/packages/core-plugin-api/src/routing/types.ts +++ b/packages/core-plugin-api/src/routing/types.ts @@ -98,6 +98,11 @@ export type RouteRef = { /** @deprecated access to this property will be removed in the future */ params: ParamKeys; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/RouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** @@ -120,6 +125,11 @@ export type SubRouteRef = { /** @deprecated access to this property will be removed in the future */ params: ParamKeys; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/SubRouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** @@ -142,6 +152,11 @@ export type ExternalRouteRef< params: ParamKeys; optional?: Optional; + + /** Compatibility field for new frontend system */ + readonly $$type: '@backstage/ExternalRouteRef'; + /** Compatibility field for new frontend system */ + readonly T: Params; }; /** diff --git a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx index b96fc5fc23..325690b6eb 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx @@ -18,140 +18,203 @@ import { renderHook } from '@testing-library/react'; import { PropsWithChildren } from 'react'; import { MemoryRouter, Router } from 'react-router-dom'; import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { + routeResolutionApiRef, + RouteResolutionApi, + RouteFunc, +} from '@backstage/frontend-plugin-api'; import { useRouteRef } from './useRouteRef'; import { createRouteRef } from './RouteRef'; +import { createExternalRouteRef } from './ExternalRouteRef'; import { createBrowserHistory } from 'history'; +import { TestApiProvider } from '@backstage/test-utils'; -describe('v1 consumer', () => { - const context = createVersionedContextForTesting('routing-context'); +describe('useRouteRef', () => { + describe('old app system', () => { + const context = createVersionedContextForTesting('routing-context'); - afterEach(() => { - context.reset(); - }); - - it('should resolve routes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); - - const routeRef = createRouteRef({ id: 'ref1' }); - - const renderedHook = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + afterEach(() => { + context.reset(); }); - const routeFunc = renderedHook.result.current; - expect(routeFunc()).toBe('/hello'); - expect(resolve).toHaveBeenCalledWith( - routeRef, - expect.objectContaining({ - pathname: '/my-page', - }), - ); - }); + it('should resolve routes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - it('re-resolves the routeFunc when the search parameters change', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + const routeRef = createRouteRef({ id: 'ref1' }); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + const renderedHook = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + const routeFunc = renderedHook.result.current; + expect(routeFunc()).toBe('/hello'); + expect(resolve).toHaveBeenCalledWith( + routeRef, + expect.objectContaining({ + pathname: '/my-page', + }), + ); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('re-resolves the routeFunc when the search parameters change', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-new-page'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(2); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc the location pathname does not change', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-new-page'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(2); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc the location pathname does not change', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc when the search parameter changes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-page'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(1); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc when the search parameter changes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page?foo=bar'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); - }); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); - it('does not re-resolve the routeFunc when the hash parameter changes', () => { - const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + expect(resolve).toHaveBeenCalledTimes(1); - const routeRef = createRouteRef({ id: 'ref1' }); - const history = createBrowserHistory(); - history.push('/my-page'); + history.push('/my-page?foo=bar'); + rerender(); - const { rerender } = renderHook(() => useRouteRef(routeRef), { - wrapper: ({ children }: PropsWithChildren<{}>) => ( - - ), + expect(resolve).toHaveBeenCalledTimes(1); }); - expect(resolve).toHaveBeenCalledTimes(1); + it('does not re-resolve the routeFunc when the hash parameter changes', () => { + const resolve = jest.fn(() => () => '/hello'); + context.set({ 1: { resolve } }); - history.push('/my-page#foo'); - rerender(); + const routeRef = createRouteRef({ id: 'ref1' }); + const history = createBrowserHistory(); + history.push('/my-page'); - expect(resolve).toHaveBeenCalledTimes(1); + const { rerender } = renderHook(() => useRouteRef(routeRef), { + wrapper: ({ children }: PropsWithChildren<{}>) => ( + + ), + }); + + expect(resolve).toHaveBeenCalledTimes(1); + + history.push('/my-page#foo'); + rerender(); + + expect(resolve).toHaveBeenCalledTimes(1); + }); + }); + + describe('new app system', () => { + it('should resolve routes using routeResolutionApi', () => { + const routeRef = createRouteRef({ id: 'ref1' }); + const mockRouteFunc: RouteFunc = jest.fn(() => '/new-route'); + + const mockRouteResolutionApi: RouteResolutionApi = { + resolve: jest.fn(() => mockRouteFunc), + }; + + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useRouteRef(routeRef), { wrapper }); + + const routeFunc = renderedHook.result.current; + expect(routeFunc).toBe(mockRouteFunc); + expect(mockRouteResolutionApi.resolve).toHaveBeenCalledWith( + expect.anything(), + { sourcePath: '/my-page' }, + ); + expect(routeFunc()).toBe('/new-route'); + }); + + it('should handle optional external route refs', () => { + const externalRouteRef = createExternalRouteRef({ + id: 'external-ref', + optional: true, + }); + + const mockRouteResolutionApi: RouteResolutionApi = { + resolve: jest.fn(() => undefined), + }; + + const wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + + const renderedHook = renderHook(() => useRouteRef(externalRouteRef), { + wrapper, + }); + + expect(renderedHook.result.current).toBeUndefined(); + }); }); }); diff --git a/packages/core-plugin-api/src/routing/useRouteRef.tsx b/packages/core-plugin-api/src/routing/useRouteRef.tsx index cf292b110f..48908ada5d 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.tsx @@ -17,6 +17,11 @@ import { useMemo } from 'react'; import { matchRoutes, useLocation } from 'react-router-dom'; import { useVersionedContext } from '@backstage/version-bridge'; +import { + RouteResolutionApi, + routeResolutionApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; import { AnyParams, ExternalRouteRef, @@ -38,6 +43,14 @@ export interface RouteResolver { ): RouteFunc | undefined; } +function useRouteResolutionApi(): RouteResolutionApi | undefined { + try { + return useApi(routeResolutionApiRef); + } catch { + return undefined; + } +} + /** * React hook for constructing URLs to routes. * @@ -86,30 +99,52 @@ export function useRouteRef( | ExternalRouteRef, ): RouteFunc | undefined { const { pathname } = useLocation(); + const routeResolutionApi = useRouteResolutionApi(); const versionedContext = useVersionedContext<{ 1: RouteResolver }>( 'routing-context', ); - if (!versionedContext) { - throw new Error('Routing context is not available'); - } - const resolver = versionedContext.atVersion(1); - const routeFunc = useMemo( + const resolver = versionedContext?.atVersion(1); + + const newRouteFunc = useMemo(() => { + if (!routeResolutionApi) { + return null; + } + + try { + return routeResolutionApi?.resolve(routeRef, { + sourcePath: pathname, + }); + } catch { + return null; + } + }, [routeResolutionApi, routeRef, pathname]); + + const legacyRouteFunc = useMemo( () => resolver && resolver.resolve(routeRef, { pathname }), [resolver, routeRef, pathname], ); - if (!versionedContext) { - throw new Error('useRouteRef used outside of routing context'); + if (newRouteFunc !== null) { + const isOptional = 'optional' in routeRef && routeRef.optional; + if (!newRouteFunc && !isOptional) { + throw new Error(`No path for ${routeRef}`); + } + return newRouteFunc; } + + if (!versionedContext) { + throw new Error('Routing context is not available'); + } + if (!resolver) { throw new Error('RoutingContext v1 not available'); } const isOptional = 'optional' in routeRef && routeRef.optional; - if (!routeFunc && !isOptional) { + if (!legacyRouteFunc && !isOptional) { throw new Error(`No path for ${routeRef}`); } - return routeFunc; + return legacyRouteFunc; } diff --git a/packages/core-plugin-api/src/translation/index.ts b/packages/core-plugin-api/src/translation/index.ts deleted file mode 100644 index 121ec62c72..0000000000 --- a/packages/core-plugin-api/src/translation/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - type TranslationMessages, - type TranslationMessagesOptions, - createTranslationMessages, -} from './TranslationMessages'; -export { - type TranslationResource, - type TranslationResourceOptions, - createTranslationResource, -} from './TranslationResource'; -export { - type TranslationRef, - type TranslationRefOptions, - createTranslationRef, -} from './TranslationRef'; -export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 446a3a557e..8c7fd970ab 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/create-app +## 0.7.7-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 0.7.7-next.0 + +### Patch Changes + +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + +## 0.7.6 + +### Patch Changes + +- 2c1fe37: Bumped create-app version. +- 20fae88: Bumped create-app version. +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. +- fc7cbfc: Added the new `@backstage/cli/templates/catalog-provider-module` template to the explicit template configuration for the `next-app` template. + +## 0.7.6-next.3 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.6-next.2 + +### Patch Changes + +- 9f939a6: Added `@backstage/plugin-app-visualizer` to the app in the `--next` template. + +## 0.7.6-next.1 + +### Patch Changes + +- Bumped create-app version. + ## 0.7.6-next.0 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 2f5ac3e944..af82d507d2 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.7.6-next.0", + "version": "0.7.7-next.1", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" @@ -58,7 +58,7 @@ "@types/command-exists": "^1.2.0", "@types/fs-extra": "^11.0.0", "@types/inquirer": "^8.1.3", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/recursive-readdir": "^2.2.0", "msw": "^2.0.0", "nodemon": "^3.0.1" diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 230590e99c..0ab31dd5f4 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { bootstrapEnvProxyAgents } from '@backstage/cli-common'; + +bootstrapEnvProxyAgents(); import chalk from 'chalk'; import fs from 'fs-extra'; diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 6785fb9b52..51fa8dff84 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -53,6 +53,7 @@ import { version as repoTools } from '../../../repo-tools/package.json'; import { version as ui } from '../../../ui/package.json'; import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json'; +import { version as pluginAppVisualizer } from '../../../../plugins/app-visualizer/package.json'; import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json'; import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json'; import { version as pluginAuthBackendModuleGithubProvider } from '../../../../plugins/auth-backend-module-github-provider/package.json'; @@ -117,6 +118,7 @@ export const packageVersions = { '@backstage/repo-tools': repoTools, '@backstage/plugin-api-docs': pluginApiDocs, '@backstage/plugin-app-backend': pluginAppBackend, + '@backstage/plugin-app-visualizer': pluginAppVisualizer, '@backstage/plugin-auth-backend': pluginAuthBackend, '@backstage/plugin-auth-backend-module-github-provider': pluginAuthBackendModuleGithubProvider, diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 8399c6505a..d0a088a7d1 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "scripts": { "start": "backstage-cli repo start", diff --git a/packages/create-app/templates/next-app/package.json.hbs b/packages/create-app/templates/next-app/package.json.hbs index 33e76aba3b..55a591067b 100644 --- a/packages/create-app/templates/next-app/package.json.hbs +++ b/packages/create-app/templates/next-app/package.json.hbs @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "engines": { - "node": "20 || 22" + "node": "22 || 24" }, "scripts": { "start": "backstage-cli repo start", diff --git a/packages/create-app/templates/next-app/packages/app/package.json.hbs b/packages/create-app/templates/next-app/packages/app/package.json.hbs index bc02fdc21a..726983fd69 100644 --- a/packages/create-app/templates/next-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/next-app/packages/app/package.json.hbs @@ -21,6 +21,7 @@ "@backstage/frontend-defaults": "^{{ version '@backstage/frontend-defaults'}}", "@backstage/frontend-plugin-api": "^{{ version '@backstage/frontend-plugin-api'}}", "@backstage/integration-react": "^{{ version '@backstage/integration-react'}}", + "@backstage/plugin-app-visualizer": "^{{ version '@backstage/plugin-app-visualizer'}}", "@backstage/plugin-catalog": "^{{ version '@backstage/plugin-catalog'}}", "@backstage/plugin-notifications": "^{{ version '@backstage/plugin-notifications'}}", "@backstage/plugin-org": "^{{ version '@backstage/plugin-org'}}", diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index f9847fed5c..1b26653336 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/dev-utils +## 1.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.1 + +## 1.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-react@1.21.4-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/catalog-model@1.7.6 + +## 1.1.17 + +### Patch Changes + +- b29a856: Fixed styling of the dev app by adding a lazy import of `@backstage/ui/css/styles.css`. +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/plugin-catalog-react@1.21.3 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/catalog-model@1.7.6 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + +## 1.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + ## 1.1.17-next.0 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index cb20f0cd8e..5b0c9bbee7 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.17-next.0", + "version": "1.1.18-next.1", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index a10481c28d..e65f1ffaf5 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,21 @@ # e2e-test +## 0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16-next.0 + - @backstage/create-app@0.7.7-next.0 + - @backstage/errors@1.2.7 + +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.6 + ## 0.2.34-next.0 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 116c219359..e70f2f8105 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.34-next.0", + "version": "0.2.35-next.0", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" @@ -47,7 +47,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/fs-extra": "^11.0.0", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "nodemon": "^3.0.1" } } diff --git a/packages/e2e-test/src/commands/index.ts b/packages/e2e-test/src/commands/index.ts index fd0ba59c30..cbcb9f3907 100644 --- a/packages/e2e-test/src/commands/index.ts +++ b/packages/e2e-test/src/commands/index.ts @@ -15,12 +15,12 @@ */ import { Command } from 'commander'; -import { run } from './run'; +import { runCommand } from './runCommand'; export function registerCommands(program: Command) { program .command('run') .option('--keep', 'Do not remove the temporary dir after tests complete') .description('Run e2e tests') - .action(run); + .action(runCommand); } diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/runCommand.ts similarity index 94% rename from packages/e2e-test/src/commands/run.ts rename to packages/e2e-test/src/commands/runCommand.ts index 6df58349df..112937dbe7 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/runCommand.ts @@ -22,18 +22,12 @@ import killTree from 'tree-kill'; import { resolve as resolvePath, join as joinPath } from 'path'; import path from 'path'; -import { - spawnPiped, - runPlain, - waitFor, - waitForExit, - print, -} from '../lib/helpers'; +import { waitFor, print } from '../lib/helpers'; import mysql from 'mysql2/promise'; import pgtools from 'pgtools'; -import { findPaths } from '@backstage/cli-common'; +import { findPaths, runOutput, run } from '@backstage/cli-common'; import { OptionValues } from 'commander'; // eslint-disable-next-line no-restricted-syntax @@ -46,7 +40,7 @@ const templatePackagePaths = [ 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', ]; -export async function run(opts: OptionValues) { +export async function runCommand(opts: OptionValues) { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); print(`CLI E2E test root: ${rootDir}\n`); @@ -67,7 +61,7 @@ export async function run(opts: OptionValues) { await createPlugin({ appDir, pluginId, select: 'backend-plugin' }); print(`Running 'yarn test:e2e' in newly created app with new plugin`); - await runPlain(['yarn', 'test:e2e'], { + await runOutput(['yarn', 'test:e2e'], { cwd: appDir, env: { ...process.env, CI: undefined }, }); @@ -75,13 +69,13 @@ export async function run(opts: OptionValues) { await switchToReact17(appDir); print(`Running 'yarn install' to install React 17`); - await runPlain(['yarn', 'install'], { cwd: appDir }); + await runOutput(['yarn', 'install'], { cwd: appDir }); print(`Running 'yarn tsc' with React 17`); - await runPlain(['yarn', 'tsc'], { cwd: appDir }); + await runOutput(['yarn', 'tsc'], { cwd: appDir }); print(`Running 'yarn test:e2e' with React 17`); - await runPlain(['yarn', 'test:e2e'], { + await runOutput(['yarn', 'test:e2e'], { cwd: appDir, env: { ...process.env, CI: undefined }, }); @@ -190,7 +184,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { appendDeps(require('@backstage/create-app/package.json')); print(`Preparing workspace`); - await runPlain([ + await runOutput([ 'yarn', 'backstage-cli', 'build-workspace', @@ -209,7 +203,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { } print('Installing workspace dependencies'); - await runPlain(['yarn', 'workspaces', 'focus', '--all', '--production'], { + await runOutput(['yarn', 'workspaces', 'focus', '--all', '--production'], { cwd: workspaceDir, }); @@ -257,7 +251,7 @@ async function createApp( workspaceDir: string, rootDir: string, ) { - const child = spawnPiped( + const child = run( [ 'node', resolvePath(workspaceDir, 'packages/create-app/bin/backstage-create-app'), @@ -265,6 +259,7 @@ async function createApp( ], { cwd: rootDir, + stdio: ['pipe', 'pipe', 'pipe'], }, ); @@ -278,7 +273,7 @@ async function createApp( child.stdin?.write(`${appName}\n`); print('Waiting for app create script to be done'); - await waitForExit(child); + await child.waitForExit(); const appDir = resolvePath(rootDir, appName); @@ -318,7 +313,7 @@ async function createApp( 'test:all', ]) { print(`Running 'yarn ${cmd}' in newly created app`); - await runPlain(['yarn', cmd], { cwd: appDir }); + await runOutput(['yarn', cmd], { cwd: appDir }); } return appDir; @@ -378,10 +373,11 @@ async function createPlugin(options: { select: string; }) { const { appDir, pluginId, select } = options; - const child = spawnPiped( + const child = run( ['yarn', 'new', '--select', select, '--option', `pluginId=${pluginId}`], { cwd: appDir, + stdio: ['pipe', 'pipe', 'pipe'], }, ); @@ -392,7 +388,7 @@ async function createPlugin(options: { }); print('Waiting for plugin create script to be done'); - await waitForExit(child); + await child.waitForExit(); const pluginDir = resolvePath( appDir, @@ -401,11 +397,11 @@ async function createPlugin(options: { ); print(`Running 'yarn tsc' in root for newly created plugin`); - await runPlain(['yarn', 'tsc'], { cwd: appDir }); + await runOutput(['yarn', 'tsc'], { cwd: appDir }); for (const cmd of [['lint'], ['test', '--no-watch']]) { print(`Running 'yarn ${cmd.join(' ')}' in newly created plugin`); - await runPlain(['yarn', ...cmd], { cwd: pluginDir }); + await runOutput(['yarn', ...cmd], { cwd: pluginDir }); } } finally { child.kill(); @@ -494,7 +490,7 @@ async function dropClientDatabases(client: string) { * Start serving the newly created backend and make sure that all db migrations works correctly */ async function testBackendStart(appDir: string, ...args: string[]) { - const child = spawnPiped(['yarn', 'workspace', 'backend', 'start', ...args], { + const child = run(['yarn', 'workspace', 'backend', 'start', ...args], { cwd: appDir, // Windows does not like piping stdin here, the child process will hang when requiring the 'process' module stdio: ['ignore', 'pipe', 'pipe'], @@ -586,7 +582,7 @@ async function testBackendStart(appDir: string, ...args: string[]) { } try { - await waitForExit(child); + await child.waitForExit(); } catch (error) { if (!successful) { throw new Error(`Backend failed to startup: ${stderr}`); diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/index.ts index f3c63ae175..df299628b2 100644 --- a/packages/e2e-test/src/index.ts +++ b/packages/e2e-test/src/index.ts @@ -18,7 +18,6 @@ import { program } from 'commander'; import chalk from 'chalk'; import { registerCommands } from './commands'; import { version } from '../package.json'; -import { exitWithError } from './lib/helpers'; async function main(argv: string[]) { program.name('e2e-test').version(version); @@ -36,4 +35,12 @@ async function main(argv: string[]) { program.parse(argv); } -main(process.argv).catch(exitWithError); +main(process.argv).catch(err => { + process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); + + if (typeof err.code === 'number') { + process.exit(err.code); + } else { + process.exit(1); + } +}); diff --git a/packages/e2e-test/src/lib/helpers.ts b/packages/e2e-test/src/lib/helpers.ts index fc5aefcff9..d310812794 100644 --- a/packages/e2e-test/src/lib/helpers.ts +++ b/packages/e2e-test/src/lib/helpers.ts @@ -14,77 +14,6 @@ * limitations under the License. */ -import { assertError } from '@backstage/errors'; -import { - spawn, - execFile as execFileCb, - SpawnOptions, - ChildProcess, -} from 'child_process'; -import { promisify } from 'util'; - -const execFile = promisify(execFileCb); - -export function spawnPiped(cmd: string[], options?: SpawnOptions) { - function pipeWithPrefix(stream: NodeJS.WriteStream, prefix = '') { - return (data: Buffer) => { - const prefixedMsg = data - .toString('utf8') - .trimEnd() - .replace(/^/gm, prefix); - stream.write(`${prefixedMsg}\n`, 'utf8'); - }; - } - - const child = spawn(cmd[0], cmd.slice(1), { - stdio: 'pipe', - shell: true, - ...options, - }); - child.on('error', exitWithError); - - const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' '); - child.stdout?.on( - 'data', - pipeWithPrefix(process.stdout, `[${logPrefix}].out: `), - ); - child.stderr?.on( - 'data', - pipeWithPrefix(process.stderr, `[${logPrefix}].err: `), - ); - - return child; -} - -export async function runPlain(cmd: string[], options?: SpawnOptions) { - try { - const { stdout } = await execFile(cmd[0], cmd.slice(1), { - ...options, - shell: true, - }); - return stdout.trim(); - } catch (error) { - assertError(error); - if (error.stdout) { - process.stdout.write(error.stdout as Buffer); - } - if (error.stderr) { - process.stderr.write(error.stderr as Buffer); - } - throw error; - } -} - -export function exitWithError(err: Error & { code?: unknown }) { - process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); - - if (typeof err.code === 'number') { - process.exit(err.code); - } else { - process.exit(1); - } -} - /** * Waits for fn() to be true * Checks every 100ms @@ -108,22 +37,6 @@ export function waitFor(fn: () => boolean, maxSeconds: number = 120) { }); } -export async function waitForExit(child: ChildProcess) { - if (child.exitCode !== null) { - throw new Error(`Child already exited with code ${child.exitCode}`); - } - await new Promise((resolve, reject) => - child.once('exit', code => { - if (code) { - reject(new Error(`Child exited with code ${code}`)); - } else { - print('Child finished'); - resolve(); - } - }), - ); -} - export function print(msg: string) { return process.stdout.write(`${msg}\n`); } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 65acedd942..a823de9ee5 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/eslint-plugin +## 0.2.0 + +### Minor Changes + +- 926389b: Added `@backstage/no-ui-css-imports-in-non-frontend` rule, which ensures that CSS from `@backstage/ui` is not imported outside of the frontend app. + ## 0.2.0-next.0 ### Minor Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index f7dc9eebcc..13db908e16 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.2.0-next.0", + "version": "0.2.0", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 38ec33c33a..2d46087f47 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/frontend-app-api +## 0.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.2 + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/frontend-defaults@0.3.3 + ## 0.13.2-next.0 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 9bdf329f0f..f69d3d1f13 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.13.2-next.0", + "version": "0.13.3-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-app-api/src/routing/RouteAliasResolver.ts b/packages/frontend-app-api/src/routing/RouteAliasResolver.ts index 64e5eb7ce8..badf27f964 100644 --- a/packages/frontend-app-api/src/routing/RouteAliasResolver.ts +++ b/packages/frontend-app-api/src/routing/RouteAliasResolver.ts @@ -16,8 +16,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { RouteRefsById } from './collectRouteIds'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef'; +import { OpaqueRouteRef } from '@internal/frontend'; /** * @internal @@ -41,7 +40,7 @@ export function createRouteAliasResolver( let currentRef = routeRef; for (let i = 0; i < 100; i++) { - const alias = toInternalRouteRef(currentRef).alias; + const alias = OpaqueRouteRef.toInternal(currentRef).alias; if (alias) { if (pluginId) { const [aliasPluginId] = alias.split('.'); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.test.ts b/packages/frontend-app-api/src/routing/RouteResolver.test.ts index 1b23ac7131..7b9105f2ae 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.test.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.test.ts @@ -22,7 +22,12 @@ import { RouteRef, SubRouteRef, } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { + BackstagePlugin, + createRouteRef as createLegacyRouteRef, + createSubRouteRef as createLegacySubRouteRef, + createExternalRouteRef as createLegacyExternalRouteRef, +} from '@backstage/core-plugin-api'; import { RouteResolver } from './RouteResolver'; import { MATCH_ALL_ROUTE } from './extractRouteInfoFromAppNode'; import { @@ -416,4 +421,196 @@ describe('RouteResolver', () => { '/my-parent/a%2F%23%26%3Fb', ); }); + + describe('with legacy route refs', () => { + const legacyRef1 = createLegacyRouteRef({ id: 'ref1' }); + const legacyRef2 = createLegacyRouteRef({ id: 'ref2', params: ['x'] }); + const legacyRef3 = createLegacyRouteRef({ id: 'ref3', params: ['y'] }); + const legacySubRef1 = createLegacySubRouteRef({ + id: 'sub1', + parent: legacyRef1, + path: '/foo', + }); + const legacySubRef2 = createLegacySubRouteRef({ + id: 'sub2', + parent: legacyRef1, + path: '/foo/:a', + }); + const legacySubRef3 = createLegacySubRouteRef({ + id: 'sub3', + parent: legacyRef2, + path: '/bar', + }); + const legacySubRef4 = createLegacySubRouteRef({ + id: 'sub4', + parent: legacyRef2, + path: '/bar/:a', + }); + const legacyExternalRef1 = createLegacyExternalRouteRef({ + id: 'external1', + }); + const legacyExternalRef2 = createLegacyExternalRouteRef({ + id: 'external2', + params: ['x'], + }); + + it('should not resolve anything with an empty resolver', () => { + const r = new RouteResolver( + new Map(), + new Map(), + [], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe(undefined); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '5x' })).toBe( + undefined, + ); + }); + + it('should resolve an absolute route', () => { + const r = new RouteResolver( + new Map([[legacyRef1, 'my-route']]), + new Map(), + [{ routeRefs: new Set([legacyRef1]), path: 'my-route', ...rest }], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '5x' })).toBe( + undefined, + ); + }); + + it('should resolve an absolute route with a param and with a parent', () => { + const r = new RouteResolver( + new Map([ + [legacyRef1, 'my-route'], + [legacyRef2, 'my-parent/:x'], + ]), + new Map([[legacyRef2, legacyRef1]]), + [ + { + routeRefs: new Set([legacyRef2]), + path: 'my-parent/:x', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { routeRefs: new Set([legacyRef1]), path: 'my-route', ...rest }, + ], + }, + ], + new Map([ + [legacyExternalRef1, legacyRef1], + [legacyExternalRef2, legacySubRef3], + ]), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyRef2, src('/'))?.({ x: '1x' })).toBe( + '/my-route/my-parent/1x', + ); + expect(r.resolve(legacySubRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(legacySubRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(legacySubRef3, src('/'))?.({ x: '3x' })).toBe( + '/my-route/my-parent/3x/bar', + ); + expect(r.resolve(legacySubRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + '/my-route/my-parent/4x/bar/4a', + ); + expect(r.resolve(legacyExternalRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(legacyExternalRef2, src('/'))?.({ x: '6x' })).toBe( + '/my-route/my-parent/6x/bar', + ); + }); + + it('should resolve the most specific match', () => { + const r = new RouteResolver( + new Map([ + [legacyRef1, 'deep'], + [legacyRef2, 'root/:x'], + [legacyRef3, 'sub/:y'], + ]), + new Map([ + [legacyRef3, legacyRef2], + [legacyRef1, legacyRef3], + ]), + [ + { + routeRefs: new Set([legacyRef2]), + path: 'root/:x', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { + routeRefs: new Set([legacyRef3]), + path: 'sub/:y', + ...rest, + children: [ + MATCH_ALL_ROUTE, + { + routeRefs: new Set([legacyRef1]), + path: 'deep', + ...rest, + }, + ], + }, + ], + }, + ], + new Map(), + '', + emptyResolver, + new Map(), + ); + + expect(r.resolve(legacyRef2, src('/'))?.({ x: 'x' })).toBe('/root/x'); + expect(r.resolve(legacyRef3, src('/root/x'))?.({ y: 'y' })).toBe( + '/root/x/sub/y', + ); + + expect(() => r.resolve(legacyRef1, src('/'))?.()).toThrow( + /^Cannot route.*with parent.*as it has parameters$/, + ); + expect(() => r.resolve(legacyRef1, src('/root/x'))?.()).toThrow( + /^Cannot route.*with parent.*as it has parameters$/, + ); + expect(r.resolve(legacyRef1, src('/root/x/sub/y'))?.()).toBe( + '/root/x/sub/y/deep', + ); + // Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here + expect( + r.resolve(legacyRef1, src('/root/x/sub/y/any/nested/path/here'))?.(), + ).toBe('/root/x/sub/y/deep'); + }); + }); }); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.ts b/packages/frontend-app-api/src/routing/RouteResolver.ts index 4c0ad0e3fa..7fd7ef934c 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.ts @@ -25,18 +25,11 @@ import { } from '@backstage/frontend-plugin-api'; import mapValues from 'lodash/mapValues'; import { AnyRouteRef, BackstageRouteObject } from './types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { isRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - isSubRouteRef, - toInternalSubRouteRef, -} from '../../../frontend-plugin-api/src/routing/SubRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - isExternalRouteRef, - toInternalExternalRouteRef, -} from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; + OpaqueRouteRef, + OpaqueExternalRouteRef, + OpaqueSubRouteRef, +} from '@internal/frontend'; import { RouteAliasResolver } from './RouteAliasResolver'; // Joins a list of paths together, avoiding trailing and duplicate slashes @@ -65,10 +58,10 @@ function resolveTargetRef( let ref: AnyRouteRef = targetRouteRef; let path = ''; - if (isExternalRouteRef(ref)) { + if (OpaqueExternalRouteRef.isType(ref)) { let resolvedRoute = routeBindings.get(ref); if (!resolvedRoute) { - const internal = toInternalExternalRouteRef(ref); + const internal = OpaqueExternalRouteRef.toInternal(ref); const defaultTarget = internal.getDefaultTarget(); if (defaultTarget) { resolvedRoute = routeRefsById.get(defaultTarget); @@ -80,13 +73,13 @@ function resolveTargetRef( ref = resolvedRoute; } - if (isSubRouteRef(ref)) { - const internal = toInternalSubRouteRef(ref); + if (OpaqueSubRouteRef.isType(ref)) { + const internal = OpaqueSubRouteRef.toInternal(ref); path = ref.path; ref = internal.getParent(); } - if (!isRouteRef(ref)) { + if (!OpaqueRouteRef.isType(ref)) { throw new Error( `Unexpectedly resolved ${targetRouteRef} to a non-route ref ${ref}`, ); diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts index b1bab3fbbe..9575603bf4 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts @@ -39,10 +39,10 @@ describe('collectRouteIds', () => { const extRef = createExternalRouteRef(); expect(String(ref)).toMatch( - /^RouteRef\{created at '.*collectRouteIds\.test\.ts.*'\}$/, + /^routeRef\{id=undefined,at='.*collectRouteIds\.test\.ts.*'\}$/, ); expect(String(extRef)).toMatch( - /^ExternalRouteRef\{created at '.*collectRouteIds\.test\.ts.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*collectRouteIds\.test\.ts.*'\}$/, ); const collected = collectRouteIds( @@ -62,8 +62,12 @@ describe('collectRouteIds', () => { 'test.extRef': extRef, }); - expect(String(ref)).toBe('RouteRef{test.ref}'); - expect(String(extRef)).toBe('ExternalRouteRef{test.extRef}'); + expect(String(ref)).toMatch( + /^routeRef\{id=test.ref,at='.*collectRouteIds\.test\.ts.*'\}$/, + ); + expect(String(extRef)).toMatch( + /^externalRouteRef\{id=test.extRef,at='.*collectRouteIds\.test\.ts.*'\}$/, + ); }); it('should report duplicate route IDs', () => { diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 79afb07928..c99c28ac1a 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -20,16 +20,12 @@ import { ExternalRouteRef, FrontendFeature, } from '@backstage/frontend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - isRouteRef, - toInternalRouteRef, -} from '../../../frontend-plugin-api/src/routing/RouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; -import { OpaqueFrontendPlugin } from '@internal/frontend'; + OpaqueRouteRef, + OpaqueSubRouteRef, + OpaqueExternalRouteRef, + OpaqueFrontendPlugin, +} from '@internal/frontend'; import { ErrorCollector } from '../wiring/createErrorCollector'; /** @internal */ @@ -62,12 +58,12 @@ export function collectRouteIds( continue; } - if (isRouteRef(ref)) { - const internalRef = toInternalRouteRef(ref); + if (OpaqueRouteRef.isType(ref)) { + const internalRef = OpaqueRouteRef.toInternal(ref); internalRef.setId(refId); routesById.set(refId, ref); } else { - const internalRef = toInternalSubRouteRef(ref); + const internalRef = OpaqueSubRouteRef.toInternal(ref); routesById.set(refId, internalRef); } } @@ -82,7 +78,7 @@ export function collectRouteIds( continue; } - const internalRef = toInternalExternalRouteRef(ref); + const internalRef = OpaqueExternalRouteRef.toInternal(ref); internalRef.setId(refId); externalRoutesById.set(refId, ref); } diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index efcc9ef18c..8375cf6d3a 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -636,7 +636,7 @@ describe('discovery', () => { }, ), ).toThrow( - /Refused to resolve alias 'other.root' for RouteRef{created at 'at .*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'} as it points to a different plugin, the expected plugin is 'test' but the alias points to 'other'/, + /Refused to resolve alias 'other.root' for routeRef{id=undefined,at='.*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'} as it points to a different plugin, the expected plugin is 'test' but the alias points to 'other'/, ); }); @@ -662,7 +662,7 @@ describe('discovery', () => { }, ), ).toThrow( - /Alias loop detected for RouteRef{created at 'at .*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'}/, + /Alias loop detected for routeRef{id=undefined,at='.*extractRouteInfoFromAppNode\.test\.ts:\d+:\d+'}/, ); }); }); diff --git a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts index f9836d7107..44fe4ecbf5 100644 --- a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts +++ b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts @@ -23,8 +23,7 @@ import { RouteRefsById } from './collectRouteIds'; import { ErrorCollector } from '../wiring/createErrorCollector'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; /** * Extracts a union of the keys in a map whose value extends the given type @@ -166,7 +165,7 @@ export function resolveRouteBindings( for (const externalRef of routesById.externalRoutes.values()) { if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) { const defaultRefId = - toInternalExternalRouteRef(externalRef).getDefaultTarget(); + OpaqueExternalRouteRef.toInternal(externalRef).getDefaultTarget(); if (defaultRefId) { const defaultRef = routesById.routes.get(defaultRefId); if (defaultRef) { diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index 13ec6e8a59..a07fdf352d 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -787,14 +787,8 @@ describe('instantiateAppNodeTree', () => { inputs: { [name in string]: | undefined - | ResolvedExtensionInput< - ExtensionInput - > - | Array< - ResolvedExtensionInput< - ExtensionInput - > - >; + | ResolvedExtensionInput + | Array>; }; }) { return [ diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 39fc88c19a..04082f8520 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -243,20 +243,10 @@ function resolveV1Inputs( } function resolveV2Inputs( - inputMap: { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + inputMap: { [inputName in string]: ExtensionInput }, attachments: ReadonlyMap, parentCollector: ErrorCollector<{ node: AppNode }>, -): ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; -}> { +): ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput }> { return mapValues(inputMap, (input, inputName) => { const attachedNodes = attachments.get(inputName) ?? []; const collector = parentCollector.child({ inputName }); @@ -297,12 +287,7 @@ function resolveV2Inputs( collector, ), ); - }) as ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }>; + }) as ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput }>; } /** @internal */ diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 2eacd0ae67..3ccdf5258f 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -44,6 +44,7 @@ function makeExt( version: 'v1', id, attachTo: { id: attachId, input: 'default' }, + inputs: {}, disabled: status === 'disabled', toString: expect.any(Function), } as Extension; @@ -61,6 +62,7 @@ function makeExtDef( name, attachTo: { id: attachId, input: 'default' }, disabled: status === 'disabled', + inputs: {}, override: () => ({} as ExtensionDefinition), } as ExtensionDefinition; } diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 651bdeb691..aeacb83d31 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/frontend-defaults +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/core-components@0.18.3 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + ## 0.3.3-next.0 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 3fcbb2c990..17b11a391d 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.3.3-next.0", + "version": "0.3.4-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index cc2c421a4e..43df44d8dc 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/config@1.3.6 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/config@1.3.6 + ## 0.1.7-next.0 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index ce61447770..11a10cfee6 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.7-next.0", + "version": "0.1.8-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 7b31d280cb..d1adc69391 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/frontend +## 0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + ## 0.0.15-next.0 ### Patch Changes diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 37727cda97..4ac3285d9f 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.15-next.0", + "version": "0.0.16-next.0", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-internal/src/index.ts b/packages/frontend-internal/src/index.ts index a5728f2ff6..38bfdc53f8 100644 --- a/packages/frontend-internal/src/index.ts +++ b/packages/frontend-internal/src/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export * from './routing'; export * from './wiring'; diff --git a/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts new file mode 100644 index 0000000000..9e70dc496a --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueExternalRouteRef.ts @@ -0,0 +1,34 @@ +/* + * 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 { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueExternalRouteRef = OpaqueType.create<{ + public: ExternalRouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getDescription(): string; + getDefaultTarget(): string | undefined; + + setId(id: string): void; + }; +}>({ + type: '@backstage/ExternalRouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/OpaqueRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueRouteRef.ts new file mode 100644 index 0000000000..83126de8e2 --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueRouteRef.ts @@ -0,0 +1,35 @@ +/* + * 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 { RouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueRouteRef = OpaqueType.create<{ + public: RouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getDescription(): string; + + alias: string | undefined; + + setId(id: string): void; + }; +}>({ + type: '@backstage/RouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/OpaqueSubRouteRef.ts b/packages/frontend-internal/src/routing/OpaqueSubRouteRef.ts new file mode 100644 index 0000000000..a8559f76a2 --- /dev/null +++ b/packages/frontend-internal/src/routing/OpaqueSubRouteRef.ts @@ -0,0 +1,32 @@ +/* + * 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 { RouteRef, SubRouteRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueSubRouteRef = OpaqueType.create<{ + public: SubRouteRef; + versions: { + readonly version: 'v1'; + + getParams(): string[]; + getParent(): RouteRef; + getDescription(): string; + }; +}>({ + type: '@backstage/SubRouteRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/routing/index.ts b/packages/frontend-internal/src/routing/index.ts new file mode 100644 index 0000000000..cc8c0ee5b8 --- /dev/null +++ b/packages/frontend-internal/src/routing/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { OpaqueRouteRef } from './OpaqueRouteRef'; +export { OpaqueSubRouteRef } from './OpaqueSubRouteRef'; +export { OpaqueExternalRouteRef } from './OpaqueExternalRouteRef'; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts index 83d037902b..b7c3674f9a 100644 --- a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts +++ b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts @@ -17,10 +17,10 @@ import { ApiHolder, AppNode, - ExtensionAttachToSpec, + ExtensionDefinitionAttachTo, ExtensionDataValue, ExtensionDataRef, - ExtensionDefinition, + OverridableExtensionDefinition, ExtensionDefinitionParameters, ExtensionInput, PortableSchema, @@ -29,14 +29,14 @@ import { import { OpaqueType } from '@internal/opaque'; export const OpaqueExtensionDefinition = OpaqueType.create<{ - public: ExtensionDefinition; + public: OverridableExtensionDefinition; versions: | { readonly version: 'v1'; readonly kind?: string; readonly namespace?: string; readonly name?: string; - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionDefinitionAttachTo; readonly disabled: boolean; readonly configSchema?: PortableSchema; readonly inputs: { @@ -67,25 +67,17 @@ export const OpaqueExtensionDefinition = OpaqueType.create<{ readonly kind?: string; readonly namespace?: string; readonly name?: string; - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionDefinitionAttachTo; readonly disabled: boolean; readonly configSchema?: PortableSchema; - readonly inputs: { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; + readonly inputs: { [inputName in string]: ExtensionInput }; readonly output: Array; factory(context: { node: AppNode; apis: ApiHolder; config: object; inputs: ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; + [inputName in string]: ExtensionInput; }>; }): Iterable>; }; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionInput.ts b/packages/frontend-internal/src/wiring/InternalExtensionInput.ts new file mode 100644 index 0000000000..9090023db9 --- /dev/null +++ b/packages/frontend-internal/src/wiring/InternalExtensionInput.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export type ExtensionInputContext = { + input: string; + kind?: string; + name?: string; +}; + +export const OpaqueExtensionInput = OpaqueType.create<{ + public: ExtensionInput; + versions: { + readonly version: undefined; + readonly context?: ExtensionInputContext; + withContext?(context: ExtensionInputContext): ExtensionInput; + }; +}>({ + type: '@backstage/ExtensionInput', + versions: [undefined], +}); diff --git a/packages/frontend-internal/src/wiring/index.ts b/packages/frontend-internal/src/wiring/index.ts index b61294cb1f..1efcc7d16d 100644 --- a/packages/frontend-internal/src/wiring/index.ts +++ b/packages/frontend-internal/src/wiring/index.ts @@ -15,6 +15,10 @@ */ export { createExtensionDataContainer } from './createExtensionDataContainer'; -export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef'; export { OpaqueExtensionDefinition } from './InternalExtensionDefinition'; +export { + OpaqueExtensionInput, + type ExtensionInputContext, +} from './InternalExtensionInput'; export { OpaqueFrontendPlugin } from './InternalFrontendPlugin'; +export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef'; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 905b5cc9c2..c0c6b6ec0e 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/frontend-plugin-api +## 0.13.2-next.0 + +### Patch Changes + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- 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/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.13.0 + +### Minor Changes + +- 7d87b4f: Renamed `ExtensionDefinition` to `OverridableExtensionDefinition` and introduced a slimmer `ExtensionDefinition` type that does not include override methods. The overridable type is generally used as an output type, while plain `ExtensionDefinition`s are used for input. This reduces type conflicts across different of `@backstage/frontend-plugin-api`, improving long-term compatibility. + +### Patch Changes + +- 4d03f08: Internal refactor of route reference implementations with minor updates to the `toString` implementations. +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + +## 0.12.2-next.2 + +### Patch Changes + +- 7c6a66d: Added support for plugin-relative `attachTo` declarations for extension definitions. This allows for the creation of extension and extension blueprints that attach to other extensions of a particular `kind` in the same plugin, rather than needing to provide the exact extension ID. This is particularly useful when wanting to provide extension blueprints with a built-in hierarchy where the extensions created from one blueprint attach to extensions created from the other blueprint, for example: + + ```ts + // kind: 'tabbed-page' + const parentPage = TabbedPageBlueprint.make({ + params: {....} + }) + // attachTo: { kind: 'tabbed-page', input: 'tabs' } + const child1 = TabContentBlueprint.make({ + name: 'tab1', + params: {....} + }) + ``` + +- Updated dependencies + - @backstage/core-components@0.18.3-next.2 + +## 0.12.2-next.1 + +### Patch Changes + +- 878c251: Updated to `ExtensionInput` to make all type parameters optional. +- Updated dependencies + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + ## 0.12.2-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 842e247e01..25c40b2e0a 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,13 +1,11 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.12.2-next.0", + "version": "0.13.2-next.0", "backstage": { "role": "web-library" }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "repository": { "type": "git", @@ -16,8 +14,19 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -31,8 +40,9 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.4", diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 52a50659b6..e45401304b 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -3,103 +3,43 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AlertApi } from '@backstage/core-plugin-api'; -import { alertApiRef } from '@backstage/core-plugin-api'; -import { AlertMessage } from '@backstage/core-plugin-api'; -import { AnyApiFactory } from '@backstage/core-plugin-api'; -import { AnyApiRef } from '@backstage/core-plugin-api'; import { AnyRouteRefParams as AnyRouteRefParams_2 } from '@backstage/frontend-plugin-api'; -import { ApiFactory } from '@backstage/core-plugin-api'; -import { ApiHolder } from '@backstage/core-plugin-api'; -import { ApiRef } from '@backstage/core-plugin-api'; -import { ApiRefConfig } from '@backstage/core-plugin-api'; -import { AppTheme } from '@backstage/core-plugin-api'; -import { AppThemeApi } from '@backstage/core-plugin-api'; -import { appThemeApiRef } from '@backstage/core-plugin-api'; -import { atlassianAuthApiRef } from '@backstage/core-plugin-api'; -import { AuthProviderInfo } from '@backstage/core-plugin-api'; -import { AuthRequestOptions } from '@backstage/core-plugin-api'; -import { BackstageIdentityApi } from '@backstage/core-plugin-api'; -import { BackstageIdentityResponse } from '@backstage/core-plugin-api'; -import { BackstageUserIdentity } from '@backstage/core-plugin-api'; -import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; -import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; +import { ApiRef as ApiRef_2 } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; -import { ConfigApi } from '@backstage/core-plugin-api'; -import { configApiRef } from '@backstage/core-plugin-api'; +import { Config } from '@backstage/config'; import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; -import { createApiFactory } from '@backstage/core-plugin-api'; -import { createApiRef } from '@backstage/core-plugin-api'; -import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; -import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; -import { discoveryApiRef } from '@backstage/core-plugin-api'; -import { ErrorApi } from '@backstage/core-plugin-api'; -import { ErrorApiError } from '@backstage/core-plugin-api'; -import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; -import { errorApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; +import { ExpandRecursive } from '@backstage/types'; import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams as ExtensionBlueprintParams_2 } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; -import { FeatureFlag } from '@backstage/core-plugin-api'; -import { FeatureFlagsApi } from '@backstage/core-plugin-api'; -import { featureFlagsApiRef } from '@backstage/core-plugin-api'; -import { FeatureFlagsSaveOptions } from '@backstage/core-plugin-api'; -import { FeatureFlagState } from '@backstage/core-plugin-api'; -import { FetchApi } from '@backstage/core-plugin-api'; -import { fetchApiRef } from '@backstage/core-plugin-api'; -import { githubAuthApiRef } from '@backstage/core-plugin-api'; -import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; -import { googleAuthApiRef } from '@backstage/core-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; -import { IconComponent as IconComponent_3 } from '@backstage/core-plugin-api'; -import { IdentityApi } from '@backstage/core-plugin-api'; -import { identityApiRef } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; +import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { JSX as JSX_3 } from 'react'; -import { microsoftAuthApiRef } from '@backstage/core-plugin-api'; -import { OAuthApi } from '@backstage/core-plugin-api'; -import { OAuthRequestApi } from '@backstage/core-plugin-api'; -import { oauthRequestApiRef } from '@backstage/core-plugin-api'; -import { OAuthRequester } from '@backstage/core-plugin-api'; -import { OAuthRequesterOptions } from '@backstage/core-plugin-api'; -import { OAuthScope } from '@backstage/core-plugin-api'; -import { oktaAuthApiRef } from '@backstage/core-plugin-api'; -import { oneloginAuthApiRef } from '@backstage/core-plugin-api'; -import { OpenIdConnectApi } from '@backstage/core-plugin-api'; -import { openshiftAuthApiRef } from '@backstage/core-plugin-api'; -import { PendingOAuthRequest } from '@backstage/core-plugin-api'; -import { ProfileInfo } from '@backstage/core-plugin-api'; -import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; +import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; -import { SessionApi } from '@backstage/core-plugin-api'; -import { SessionState } from '@backstage/core-plugin-api'; -import { SignInPageProps } from '@backstage/core-plugin-api'; -import { StorageApi } from '@backstage/core-plugin-api'; -import { storageApiRef } from '@backstage/core-plugin-api'; -import { StorageValueSnapshot } from '@backstage/core-plugin-api'; -import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; -import { TranslationMessagesOptions } from '@backstage/core-plugin-api/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { TranslationRefOptions } from '@backstage/core-plugin-api/alpha'; -import { TranslationResource } from '@backstage/core-plugin-api/alpha'; -import { TranslationResourceOptions } from '@backstage/core-plugin-api/alpha'; -import { TypesToApiRefs } from '@backstage/core-plugin-api'; -import { useApi } from '@backstage/core-plugin-api'; -import { useApiHolder } from '@backstage/core-plugin-api'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; -import { withApis } from '@backstage/core-plugin-api'; +import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api'; import { z } from 'zod'; -export { AlertApi }; +// @public +export type AlertApi = { + post(alert: AlertMessage): void; + alert$(): Observable; +}; -export { alertApiRef }; +// @public +export const alertApiRef: ApiRef; -export { AlertMessage }; +// @public +export type AlertMessage = { + message: string; + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; // @public export type AnalyticsApi = { @@ -143,12 +83,12 @@ export type AnalyticsImplementation = { }; // @public -export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ +export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{ kind: 'analytics'; params: ( params: AnalyticsImplementationFactory, - ) => ExtensionBlueprintParams>; - output: ExtensionDataRef< + ) => ExtensionBlueprintParams_2>; + output: ExtensionDataRef_2< AnalyticsImplementationFactory<{}>, 'core.analytics.factory', {} @@ -157,7 +97,7 @@ export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - factory: ConfigurableExtensionDataRef< + factory: ConfigurableExtensionDataRef_2< AnalyticsImplementationFactory<{}>, 'core.analytics.factory', {} @@ -187,9 +127,17 @@ export type AnalyticsTracker = { ) => void; }; -export { AnyApiFactory }; +// @public +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { + [key in string]: unknown; + } +>; -export { AnyApiRef }; +// @public +export type AnyApiRef = ApiRef; // @public @deprecated (undocumented) export type AnyExtensionDataRef = ExtensionDataRef; @@ -202,7 +150,7 @@ export type AnyRouteRefParams = | undefined; // @public -export const ApiBlueprint: ExtensionBlueprint<{ +export const ApiBlueprint: ExtensionBlueprint_2<{ kind: 'api'; params: < TApi, @@ -210,13 +158,13 @@ export const ApiBlueprint: ExtensionBlueprint<{ TDeps extends { [name in string]: unknown }, >( params: ApiFactory, - ) => ExtensionBlueprintParams; - output: ExtensionDataRef; + ) => ExtensionBlueprintParams_2; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; dataRefs: { - factory: ConfigurableExtensionDataRef< + factory: ConfigurableExtensionDataRef_2< AnyApiFactory, 'core.api.factory', {} @@ -224,13 +172,51 @@ export const ApiBlueprint: ExtensionBlueprint<{ }; }>; -export { ApiFactory }; +// @public +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; -export { ApiHolder }; +// @public +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; -export { ApiRef }; +// @public +export type ApiRef = { + id: string; + T: T; +}; -export { ApiRefConfig }; +// @public +export type ApiRefConfig = { + id: string; +}; + +// @public (undocumented) +export type AppLanguageApi = { + getAvailableLanguages(): { + languages: string[]; + }; + setLanguage(language?: string): void; + getLanguage(): { + language: string; + }; + language$(): Observable<{ + language: string; + }>; +}; + +// @public (undocumented) +export const appLanguageApiRef: ApiRef; // @public export interface AppNode { @@ -259,7 +245,7 @@ export interface AppNodeInstance { // @public export interface AppNodeSpec { // (undocumented) - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; // (undocumented) readonly config?: unknown; // (undocumented) @@ -273,12 +259,12 @@ export interface AppNodeSpec { } // @public -export const AppRootElementBlueprint: ExtensionBlueprint<{ +export const AppRootElementBlueprint: ExtensionBlueprint_2<{ kind: 'app-root-element'; params: { element: JSX.Element; }; - output: ExtensionDataRef; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; @@ -286,13 +272,13 @@ export const AppRootElementBlueprint: ExtensionBlueprint<{ }>; // @public -export const AppRootWrapperBlueprint: ExtensionBlueprint<{ +export const AppRootWrapperBlueprint: ExtensionBlueprint_2<{ kind: 'app-root-wrapper'; params: { Component?: [error: 'Use the `component` parameter instead']; component: (props: { children: ReactNode }) => JSX.Element | null; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< (props: { children: ReactNode }) => JSX.Element | null, 'app.root.wrapper', {} @@ -301,7 +287,7 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< (props: { children: ReactNode }) => JSX.Element | null, 'app.root.wrapper', {} @@ -309,11 +295,25 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint<{ }; }>; -export { AppTheme }; +// @public +export type AppTheme = { + id: string; + title: string; + variant: 'light' | 'dark'; + icon?: React.ReactElement; + Provider(props: { children: ReactNode }): JSX.Element | null; +}; -export { AppThemeApi }; +// @public +export type AppThemeApi = { + getInstalledThemes(): AppTheme[]; + activeThemeId$(): Observable; + getActiveThemeId(): string | undefined; + setActiveThemeId(themeId?: string): void; +}; -export { appThemeApiRef }; +// @public +export const appThemeApiRef: ApiRef; // @public export interface AppTree { @@ -333,27 +333,63 @@ export interface AppTreeApi { } // @public -export const appTreeApiRef: ApiRef; +export const appTreeApiRef: ApiRef_2; -export { atlassianAuthApiRef }; +// @public +export const atlassianAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; -export { AuthProviderInfo }; +// @public +export type AuthProviderInfo = { + id: string; + title: string; + icon: IconComponent; + message?: string; +}; -export { AuthRequestOptions }; +// @public +export type AuthRequestOptions = { + optional?: boolean; + instantPopup?: boolean; +}; -export { BackstageIdentityApi }; +// @public +export type BackstageIdentityApi = { + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; -export { BackstageIdentityResponse }; +// @public +export type BackstageIdentityResponse = { + token: string; + expiresAt?: Date; + identity: BackstageUserIdentity; +}; -export { BackstageUserIdentity }; +// @public +export type BackstageUserIdentity = { + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; +}; -export { bitbucketAuthApiRef }; +// @public +export const bitbucketAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; -export { bitbucketServerAuthApiRef }; +// @public +export const bitbucketServerAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; -export { ConfigApi }; +// @public +export type ConfigApi = Config; -export { configApiRef }; +// @public +export const configApiRef: ApiRef; // @public (undocumented) export interface ConfigurableExtensionDataRef< @@ -377,35 +413,43 @@ export interface ConfigurableExtensionDataRef< // @public (undocumented) export const coreExtensionData: { - title: ConfigurableExtensionDataRef; - reactElement: ConfigurableExtensionDataRef< + title: ConfigurableExtensionDataRef_2; + reactElement: ConfigurableExtensionDataRef_2< JSX_3.Element, 'core.reactElement', {} >; - routePath: ConfigurableExtensionDataRef; - routeRef: ConfigurableExtensionDataRef< + routePath: ConfigurableExtensionDataRef_2; + routeRef: ConfigurableExtensionDataRef_2< RouteRef, 'core.routing.ref', {} >; }; -export { createApiFactory }; +// @public +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +>(factory: ApiFactory): ApiFactory; -export { createApiRef }; +// @public +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; + +// @public +export function createApiRef(config: ApiRefConfig): ApiRef; // @public export function createExtension< UOutput extends ExtensionDataRef, TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [inputName in string]: ExtensionInput; }, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType; @@ -413,6 +457,7 @@ export function createExtension< UFactoryOutput extends ExtensionDataValue, const TKind extends string | undefined = undefined, const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, >( options: CreateExtensionOptions< TKind, @@ -420,9 +465,10 @@ export function createExtension< UOutput, TInputs, TConfigSchema, - UFactoryOutput + UFactoryOutput, + UParentInputs >, -): ExtensionDefinition<{ +): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} : { @@ -453,19 +499,14 @@ export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [inputName in string]: ExtensionInput; }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, TKind extends string, + UParentInputs extends ExtensionDataRef, TDataRefs extends { [name in string]: ExtensionDataRef; } = never, @@ -477,7 +518,8 @@ export function createExtensionBlueprint< TInputs, TConfigSchema, UFactoryOutput, - TDataRefs + TDataRefs, + UParentInputs >, ): ExtensionBlueprint<{ kind: TKind; @@ -511,13 +553,7 @@ export type CreateExtensionBlueprintOptions< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [inputName in string]: ExtensionInput; }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; @@ -526,9 +562,11 @@ export type CreateExtensionBlueprintOptions< TDataRefs extends { [name in string]: ExtensionDataRef; }, + UParentInputs extends ExtensionDataRef, > = { kind: TKind; - attachTo: ExtensionAttachToSpec; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; disabled?: boolean; inputs?: TInputs; output: Array; @@ -601,22 +639,18 @@ export type CreateExtensionOptions< TName extends string | undefined, UOutput extends ExtensionDataRef, TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [inputName in string]: ExtensionInput; }, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, + UParentInputs extends ExtensionDataRef, > = { kind?: TKind; name?: TName; - attachTo: ExtensionAttachToSpec; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; disabled?: boolean; inputs?: TInputs; output: Array; @@ -641,7 +675,7 @@ export function createExternalRouteRef< } | undefined = undefined, TParamKeys extends string = string, ->(options?: { +>(config?: { readonly params?: string extends TParamKeys ? (keyof TParams)[] : TParamKeys[]; @@ -785,11 +819,50 @@ export type CreateSwappableComponentOptions< transformProps?: (props: TExternalComponentProps) => TInnerComponentProps; }; -export { createTranslationMessages }; +// @public +export function createTranslationMessages< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, +>( + options: TranslationMessagesOptions, +): TranslationMessages; -export { createTranslationRef }; +// @public (undocumented) +export function createTranslationRef< + TId extends string, + const TNestedMessages extends AnyNestedMessages, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof FlattenedMessages]: string | null; + }; + }>; + }, +>( + config: TranslationRefOptions, +): TranslationRef>; -export { createTranslationResource }; +// @public (undocumented) +export function createTranslationResource< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +>( + options: TranslationResourceOptions, +): TranslationResource; // @public export interface DialogApi { @@ -821,24 +894,44 @@ export interface DialogApiDialog { } // @public -export const dialogApiRef: ApiRef; +export const dialogApiRef: ApiRef_2; -export { DiscoveryApi }; +// @public +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; -export { discoveryApiRef }; +// @public +export const discoveryApiRef: ApiRef; -export { ErrorApi }; +// @public +export type ErrorApi = { + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; -export { ErrorApiError }; +// @public +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; -export { ErrorApiErrorContext }; +// @public +export type ErrorApiErrorContext = { + hidden?: boolean; +}; -export { errorApiRef }; +// @public +export const errorApiRef: ApiRef; // @public (undocumented) export const ErrorDisplay: { (props: ErrorDisplayProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) @@ -863,7 +956,7 @@ export interface Extension { } // @public (undocumented) -export type ExtensionAttachToSpec = +export type ExtensionAttachTo = | { id: string; input: string; @@ -873,6 +966,9 @@ export type ExtensionAttachToSpec = input: string; }>; +// @public @deprecated (undocumented) +export type ExtensionAttachToSpec = ExtensionAttachTo; + // @public (undocumented) export interface ExtensionBlueprint< T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters, @@ -882,17 +978,19 @@ export interface ExtensionBlueprint< // (undocumented) make< TName extends string | undefined, - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, + UParentInputs extends ExtensionDataRef, >(args: { name?: TName; - attachTo?: ExtensionAttachToSpec; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo, UParentInputs>; disabled?: boolean; params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput : T['params'] extends ExtensionBlueprintDefineParams ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: defineParams => defineParams() })`' : T['params']; - }): ExtensionDefinition<{ + }): OverridableExtensionDefinition<{ kind: T['kind']; name: string | undefined extends TName ? undefined : TName; config: T['config']; @@ -908,18 +1006,19 @@ export interface ExtensionBlueprint< }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, TExtraInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }, + [inputName in string]: ExtensionInput; + } = {}, >(args: { name?: TName; - attachTo?: ExtensionAttachToSpec; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; disabled?: boolean; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & @@ -934,7 +1033,7 @@ export interface ExtensionBlueprint< }; factory( originalFactory: < - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, >( params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput @@ -963,27 +1062,31 @@ export interface ExtensionBlueprint< : UNewOutput, UFactoryOutput >; - }): ExtensionDefinition<{ - config: (string extends keyof TExtensionConfigSchema - ? {} - : { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }) & - T['config']; - configInput: (string extends keyof TExtensionConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + }): OverridableExtensionDefinition<{ + config: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType >; - }> - >) & - T['configInput']; + }) & + T['config'] + >; + configInput: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput'] + >; output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; - inputs: T['inputs'] & TExtraInputs; + inputs: Expand; kind: T['kind']; name: string | undefined extends TName ? undefined : TName; params: T['params']; @@ -1008,13 +1111,7 @@ export type ExtensionBlueprintParameters = { }; output?: ExtensionDataRef; inputs?: { - [KName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [KName in string]: ExtensionInput; }; dataRefs?: { [name in string]: ExtensionDataRef; @@ -1049,6 +1146,8 @@ export interface ExtensionBoundaryProps { // (undocumented) children: ReactNode; // (undocumented) + errorPresentation?: 'error-api' | 'error-display'; + // (undocumented) node: AppNode; } @@ -1104,111 +1203,49 @@ export type ExtensionDataValue = { }; // @public (undocumented) -export type ExtensionDefinition< - T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, -> = { +export interface ExtensionDefinition< + TParams extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> { + // (undocumented) $$type: '@backstage/ExtensionDefinition'; - readonly T: T; - override< - TExtensionConfigSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, - UFactoryOutput extends ExtensionDataValue, - UNewOutput extends ExtensionDataRef, - TExtraInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; + // (undocumented) + readonly T: TParams; +} + +// @public +export type ExtensionDefinitionAttachTo< + UParentInputs extends ExtensionDataRef = ExtensionDataRef, +> = + | { + id: string; + input: string; + relative?: never; + } + | { + relative: { + kind?: string; + name?: string; + }; + input: string; + id?: never; + } + | ExtensionInput + | Array< + | { + id: string; + input: string; + relative?: never; } - >; - }, - TParamsInput extends AnyParamsInput_2>, - >( - args: Expand< - { - attachTo?: ExtensionAttachToSpec; - disabled?: boolean; - inputs?: TExtraInputs & { - [KName in keyof T['inputs']]?: `Error: Input '${KName & - string}' is already defined in parent definition`; - }; - output?: Array; - config?: { - schema: TExtensionConfigSchema & { - [KName in keyof T['config']]?: `Error: Config key '${KName & - string}' is already defined in parent schema`; + | { + relative: { + kind?: string; + name?: string; }; - }; - factory?( - originalFactory: < - TFactoryParamsReturn extends AnyParamsInput_2< - NonNullable - >, - >( - context?: Expand< - { - config?: T['config']; - inputs?: ResolvedInputValueOverrides>; - } & ([T['params']] extends [never] - ? {} - : { - params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams - ? TFactoryParamsReturn - : T['params'] extends ExtensionBlueprintDefineParams - ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' - : Partial; - }) - >, - ) => ExtensionDataContainer>, - context: { - node: AppNode; - apis: ApiHolder; - config: T['config'] & { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; - inputs: Expand>; - }, - ): Iterable; - } & ([T['params']] extends [never] - ? {} - : { - params?: TParamsInput extends ExtensionBlueprintDefineParams - ? TParamsInput - : T['params'] extends ExtensionBlueprintDefineParams - ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' - : Partial; - }) - > & - VerifyExtensionFactoryOutput< - ExtensionDataRef extends UNewOutput - ? NonNullable - : UNewOutput, - UFactoryOutput - >, - ): ExtensionDefinition<{ - kind: T['kind']; - name: T['name']; - output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; - inputs: T['inputs'] & TExtraInputs; - config: T['config'] & { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; - configInput: T['configInput'] & - z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] - >; - }> - >; - }>; -}; + input: string; + id?: never; + } + | ExtensionInput + >; // @public (undocumented) export type ExtensionDefinitionParameters = { @@ -1222,13 +1259,7 @@ export type ExtensionDefinitionParameters = { }; output?: ExtensionDataRef; inputs?: { - [KName in string]: ExtensionInput< - ExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; + [KName in string]: ExtensionInput; }; params?: object | ExtensionBlueprintDefineParams; }; @@ -1253,20 +1284,23 @@ export interface ExtensionInput< { optional?: true; } - >, + > = ExtensionDataRef, TConfig extends { singleton: boolean; optional: boolean; + } = { + singleton: boolean; + optional: boolean; }, > { // (undocumented) - $$type: '@backstage/ExtensionInput'; + readonly $$type: '@backstage/ExtensionInput'; // (undocumented) - config: TConfig; + readonly config: TConfig; // (undocumented) - extensionData: Array; + readonly extensionData: Array; // (undocumented) - replaces?: Array<{ + readonly replaces?: Array<{ id: string; input: string; }>; @@ -1282,24 +1316,60 @@ export interface ExternalRouteRef< readonly T: TParams; } -export { FeatureFlag }; +// @public +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; // @public export type FeatureFlagConfig = { name: string; }; -export { FeatureFlagsApi }; +// @public +export interface FeatureFlagsApi { + getRegisteredFlags(): FeatureFlag[]; + isActive(name: string): boolean; + registerFlag(flag: FeatureFlag): void; + save(options: FeatureFlagsSaveOptions): void; +} -export { featureFlagsApiRef }; +// @public +export const featureFlagsApiRef: ApiRef; -export { FeatureFlagsSaveOptions }; +// @public +export type FeatureFlagsSaveOptions = { + states: Record; + merge?: boolean; +}; -export { FeatureFlagState }; +// @public +export const FeatureFlagState: { + readonly None: 0; + readonly Active: 1; +}; -export { FetchApi }; +// @public (undocumented) +export type FeatureFlagState = + (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; -export { fetchApiRef }; +// @public (undocumented) +export namespace FeatureFlagState { + // (undocumented) + export type Active = typeof FeatureFlagState.Active; + // (undocumented) + export type None = typeof FeatureFlagState.None; +} + +// @public +export type FetchApi = { + fetch: typeof fetch; +}; + +// @public +export const fetchApiRef: ApiRef; // @public (undocumented) export type FrontendFeature = FrontendPlugin | FrontendModule; @@ -1364,19 +1434,36 @@ export type FrontendPluginInfoOptions = { manifest?: () => Promise; }; -export { githubAuthApiRef }; +// @public +export const githubAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; -export { gitlabAuthApiRef }; +// @public +export const gitlabAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; -export { googleAuthApiRef }; +// @public +export const googleAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public (undocumented) -export const IconBundleBlueprint: ExtensionBlueprint<{ +export const IconBundleBlueprint: ExtensionBlueprint_2<{ kind: 'icon-bundle'; params: { icons: { [key in string]: IconComponent }; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< { [x: string]: IconComponent; }, @@ -1387,7 +1474,7 @@ export const IconBundleBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - icons: ConfigurableExtensionDataRef< + icons: ConfigurableExtensionDataRef_2< { [x: string]: IconComponent; }, @@ -1411,13 +1498,29 @@ export interface IconsApi { } // @public -export const iconsApiRef: ApiRef; +export const iconsApiRef: ApiRef_2; -export { IdentityApi }; +// @public +export type IdentityApi = { + getProfileInfo(): Promise; + getBackstageIdentity(): Promise; + getCredentials(): Promise<{ + token?: string; + }>; + signOut(): Promise; +}; -export { identityApiRef }; +// @public +export const identityApiRef: ApiRef; -export { microsoftAuthApiRef }; +// @public +export const microsoftAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public export const NavContentBlueprint: ExtensionBlueprint_2<{ @@ -1459,17 +1562,17 @@ export interface NavContentComponentProps { } // @public -export const NavItemBlueprint: ExtensionBlueprint<{ +export const NavItemBlueprint: ExtensionBlueprint_2<{ kind: 'nav-item'; params: { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1479,10 +1582,10 @@ export const NavItemBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - target: ConfigurableExtensionDataRef< + target: ConfigurableExtensionDataRef_2< { title: string; - icon: IconComponent_3; + icon: IconComponent; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1494,7 +1597,7 @@ export const NavItemBlueprint: ExtensionBlueprint<{ // @public (undocumented) export const NotFoundErrorPage: { (props: NotFoundErrorPageProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) @@ -1502,25 +1605,178 @@ export type NotFoundErrorPageProps = { children?: ReactNode; }; -export { OAuthApi }; +// @public +export type OAuthApi = { + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; -export { OAuthRequestApi }; +// @public +export type OAuthRequestApi = { + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + authRequest$(): Observable; +}; -export { oauthRequestApiRef }; +// @public +export const oauthRequestApiRef: ApiRef; -export { OAuthRequester }; +// @public +export type OAuthRequester = ( + scopes: Set, +) => Promise; -export { OAuthRequesterOptions }; +// @public +export type OAuthRequesterOptions = { + provider: AuthProviderInfo; + onAuthRequest(scopes: Set): Promise; +}; -export { OAuthScope }; +// @public +export type OAuthScope = string | string[]; -export { oktaAuthApiRef }; +// @public +export const oktaAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; -export { oneloginAuthApiRef }; +// @public +export const oneloginAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; -export { OpenIdConnectApi }; +// @public +export type OpenIdConnectApi = { + getIdToken(options?: AuthRequestOptions): Promise; +}; -export { openshiftAuthApiRef }; +// @public +export const openshiftAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; + +// @public (undocumented) +export interface OverridableExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> extends ExtensionDefinition { + readonly inputs: { + [K in keyof T['inputs']]: ExtensionInput< + T['inputs'][K] extends ExtensionInput ? IData : never + >; + }; + // (undocumented) + override< + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + }, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + configInput: T['configInput'] & + z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >; + }>; +} // @public export interface OverridableFrontendPlugin< @@ -1541,7 +1797,9 @@ export interface OverridableFrontendPlugin< }, > extends FrontendPlugin { // (undocumented) - getExtension(id: TId): TExtensionMap[TId]; + getExtension( + id: TId, + ): OverridableExtensionDefinition; // (undocumented) withOverrides(options: { extensions: Array; @@ -1550,7 +1808,7 @@ export interface OverridableFrontendPlugin< } // @public -export const PageBlueprint: ExtensionBlueprint<{ +export const PageBlueprint: ExtensionBlueprint_2<{ kind: 'page'; params: { defaultPath?: [Error: `Use the 'path' param instead`]; @@ -1559,10 +1817,10 @@ export const PageBlueprint: ExtensionBlueprint<{ routeRef?: RouteRef; }; output: - | ExtensionDataRef - | ExtensionDataRef - | ExtensionDataRef< - RouteRef, + | ExtensionDataRef_2 + | ExtensionDataRef_2 + | ExtensionDataRef_2< + RouteRef, 'core.routing.ref', { optional: true; @@ -1578,7 +1836,12 @@ export const PageBlueprint: ExtensionBlueprint<{ dataRefs: never; }>; -export { PendingOAuthRequest }; +// @public +export type PendingOAuthRequest = { + provider: AuthProviderInfo; + reject(): void; + trigger(): Promise; +}; // @public (undocumented) export interface PluginOptions< @@ -1611,32 +1874,39 @@ export type PortableSchema = { schema: JsonObject; }; -export { ProfileInfo }; +// @public +export type ProfileInfo = { + email?: string; + displayName?: string; + picture?: string; +}; -export { ProfileInfoApi }; +// @public +export type ProfileInfoApi = { + getProfile(options?: AuthRequestOptions): Promise; +}; // @public (undocumented) export const Progress: { (props: ProgressProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) export type ProgressProps = {}; // @public -export type ResolvedExtensionInput< - TExtensionInput extends ExtensionInput, -> = TExtensionInput['extensionData'] extends Array - ? { - node: AppNode; - } & ExtensionDataContainer - : never; +export type ResolvedExtensionInput = + TExtensionInput['extensionData'] extends Array + ? { + node: AppNode; + } & ExtensionDataContainer + : never; // @public export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -1654,13 +1924,13 @@ export type RouteFunc = ( ) => string; // @public (undocumented) -export const RouterBlueprint: ExtensionBlueprint<{ +export const RouterBlueprint: ExtensionBlueprint_2<{ kind: 'app-router-component'; params: { Component?: [error: 'Use the `component` parameter instead']; component: (props: { children: ReactNode }) => JSX.Element | null; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} @@ -1669,7 +1939,7 @@ export const RouterBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} @@ -1702,19 +1972,39 @@ export interface RouteResolutionApi { } // @public -export const routeResolutionApiRef: ApiRef; - -export { SessionApi }; - -export { SessionState }; +export const routeResolutionApiRef: ApiRef_2; // @public -export const SignInPageBlueprint: ExtensionBlueprint<{ +export type SessionApi = { + signIn(): Promise; + signOut(): Promise; + sessionState$(): Observable; +}; + +// @public +export const SessionState: { + readonly SignedIn: 'SignedIn'; + readonly SignedOut: 'SignedOut'; +}; + +// @public (undocumented) +export type SessionState = (typeof SessionState)[keyof typeof SessionState]; + +// @public (undocumented) +export namespace SessionState { + // (undocumented) + export type SignedIn = typeof SessionState.SignedIn; + // (undocumented) + export type SignedOut = typeof SessionState.SignedOut; +} + +// @public +export const SignInPageBlueprint: ExtensionBlueprint_2<{ kind: 'sign-in-page'; params: { loader: () => Promise>; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< ComponentType, 'core.sign-in-page.component', {} @@ -1723,7 +2013,7 @@ export const SignInPageBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< ComponentType, 'core.sign-in-page.component', {} @@ -1731,11 +2021,38 @@ export const SignInPageBlueprint: ExtensionBlueprint<{ }; }>; -export { StorageApi }; +// @public +export type SignInPageProps = { + onSignInSuccess(identityApi: IdentityApi): void; + children?: ReactNode; +}; -export { storageApiRef }; +// @public +export interface StorageApi { + forBucket(name: string): StorageApi; + observe$( + key: string, + ): Observable>; + remove(key: string): Promise; + set(key: string, data: T): Promise; + snapshot(key: string): StorageValueSnapshot; +} -export { StorageValueSnapshot }; +// @public +export const storageApiRef: ApiRef; + +// @public +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; // @public export interface SubRouteRef< @@ -1750,7 +2067,7 @@ export interface SubRouteRef< } // @public -export const SwappableComponentBlueprint: ExtensionBlueprint<{ +export const SwappableComponentBlueprint: ExtensionBlueprint_2<{ kind: 'component'; params: >(params: { component: Ref extends SwappableComponentRef< @@ -1766,7 +2083,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never; - }) => ExtensionBlueprintParams<{ + }) => ExtensionBlueprintParams_2<{ component: Ref extends SwappableComponentRef< any, infer IExternalComponentProps @@ -1781,7 +2098,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never; }>; - output: ExtensionDataRef< + output: ExtensionDataRef_2< { ref: SwappableComponentRef; loader: @@ -1795,7 +2112,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< { ref: SwappableComponentRef; loader: @@ -1831,30 +2148,51 @@ export interface SwappableComponentsApi { } // @public -export const swappableComponentsApiRef: ApiRef; +export const swappableComponentsApiRef: ApiRef_2; // @public -export const ThemeBlueprint: ExtensionBlueprint<{ +export const ThemeBlueprint: ExtensionBlueprint_2<{ kind: 'theme'; params: { theme: AppTheme; }; - output: ExtensionDataRef; + output: ExtensionDataRef_2; inputs: {}; config: {}; configInput: {}; dataRefs: { - theme: ConfigurableExtensionDataRef; + theme: ConfigurableExtensionDataRef_2; }; }>; +// @public (undocumented) +export type TranslationApi = { + getTranslation< + TMessages extends { + [key in string]: string; + }, + >( + translationRef: TranslationRef, + ): TranslationSnapshot; + translation$< + TMessages extends { + [key in string]: string; + }, + >( + translationRef: TranslationRef, + ): Observable>; +}; + +// @public (undocumented) +export const translationApiRef: ApiRef; + // @public -export const TranslationBlueprint: ExtensionBlueprint<{ +export const TranslationBlueprint: ExtensionBlueprint_2<{ kind: 'translation'; params: { resource: TranslationResource | TranslationMessages; }; - output: ExtensionDataRef< + output: ExtensionDataRef_2< | TranslationResource | TranslationMessages< string, @@ -1870,7 +2208,7 @@ export const TranslationBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - translation: ConfigurableExtensionDataRef< + translation: ConfigurableExtensionDataRef_2< | TranslationResource | TranslationMessages< string, @@ -1885,26 +2223,169 @@ export const TranslationBlueprint: ExtensionBlueprint<{ }; }>; -export { TranslationMessages }; +// @public (undocumented) +export type TranslationFunction< + TMessages extends { + [key in string]: string; + }, +> = CollapsedMessages extends infer IMessages extends { + [key in string]: string; +} + ? { + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string + > + ): IMessages[TKey]; + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX_3.Element + > + ): JSX_3.Element; + } + : never; -export { TranslationMessagesOptions }; +// @public +export interface TranslationMessages< + TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, + TFull extends boolean = boolean, +> { + // (undocumented) + $$type: '@backstage/TranslationMessages'; + full: TFull; + id: TId; + messages: TMessages; +} -export { TranslationRef }; +// @public +export interface TranslationMessagesOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TFull extends boolean, +> { + // (undocumented) + full?: TFull; + // (undocumented) + messages: false extends TFull + ? { + [key in keyof TMessages]?: string | null; + } + : { + [key in keyof TMessages]: string | null; + }; + // (undocumented) + ref: TranslationRef; +} -export { TranslationRefOptions }; +// @public (undocumented) +export interface TranslationRef< + TId extends string = string, + TMessages extends { + [key in string]: string; + } = { + [key in string]: string; + }, +> { + // (undocumented) + $$type: '@backstage/TranslationRef'; + // (undocumented) + id: TId; + // (undocumented) + T: TMessages; +} -export { TranslationResource }; +// @public (undocumented) +export interface TranslationRefOptions< + TId extends string, + TNestedMessages extends AnyNestedMessages, + TTranslations extends { + [language in string]: () => Promise<{ + default: { + [key in keyof FlattenedMessages]: string | null; + }; + }>; + }, +> { + // (undocumented) + id: TId; + // (undocumented) + messages: TNestedMessages; + // (undocumented) + translations?: TTranslations; +} -export { TranslationResourceOptions }; +// @public (undocumented) +export interface TranslationResource { + // (undocumented) + $$type: '@backstage/TranslationResource'; + // (undocumented) + id: TId; +} -export { TypesToApiRefs }; +// @public (undocumented) +export interface TranslationResourceOptions< + TId extends string, + TMessages extends { + [key in string]: string; + }, + TTranslations extends { + [language in string]: () => Promise<{ + default: + | TranslationMessages + | { + [key in keyof TMessages]: string | null; + }; + }>; + }, +> { + // (undocumented) + ref: TranslationRef; + // (undocumented) + translations: TTranslations; +} + +// @public (undocumented) +export type TranslationSnapshot< + TMessages extends { + [key in string]: string; + }, +> = + | { + ready: false; + } + | { + ready: true; + t: TranslationFunction; + }; + +// @public +export type TypesToApiRefs = { + [key in keyof T]: ApiRef; +}; // @public export function useAnalytics(): AnalyticsTracker; -export { useApi }; +// @public +export function useApi(apiRef: ApiRef): T; -export { useApiHolder }; +// @public +export function useApiHolder(): ApiHolder; // @public export function useAppNode(): AppNode | undefined; @@ -1922,9 +2403,29 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; -export { useTranslationRef }; +// @public (undocumented) +export const useTranslationRef: ( + translationRef: TranslationRef, +) => { + t: TranslationFunction; +}; -export { vmwareCloudAuthApiRef }; +// @public +export const vmwareCloudAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; -export { withApis }; +// @public +export function withApis( + apis: TypesToApiRefs, +): ( + WrappedComponent: ComponentType, +) => { + (props: PropsWithChildren>): JSX_2.Element; + displayName: string; +}; ``` diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx index 547c78a7fc..18fe64af30 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx @@ -16,7 +16,7 @@ import { renderHook } from '@testing-library/react'; import { useAnalytics } from './useAnalytics'; -import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; import { TestApiProvider } from '@backstage/test-utils'; describe('useAnalytics', () => { diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx index 397906d1d1..5c5f2ad60a 100644 --- a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useApi } from '@backstage/core-plugin-api'; +import { useApi } from '../apis/system'; import { useAnalyticsContext } from './AnalyticsContext'; import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; import { useRef } from 'react'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts index 649a930b50..afdcb6a617 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AlertApi.ts @@ -14,8 +14,43 @@ * limitations under the License. */ -export { - type AlertApi, - type AlertMessage, - alertApiRef, -} from '@backstage/core-plugin-api'; +import { createApiRef, ApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Message handled by the {@link AlertApi}. + * + * @public + */ +export type AlertMessage = { + message: string; + // Severity will default to success since that is what material ui defaults the value to. + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; + +/** + * The alert API is used to report alerts to the app, and display them to the user. + * + * @public + */ +export type AlertApi = { + /** + * Post an alert for handling by the application. + */ + post(alert: AlertMessage): void; + + /** + * Observe alerts posted by other parts of the application. + */ + alert$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link AlertApi}. + * + * @public + */ +export const alertApiRef: ApiRef = createApiRef({ + id: 'core.alert', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts index 2040966464..aa1f08ffbe 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; import { AnalyticsContextValue } from '../../analytics/types'; -import type { AnalyticsImplementationBlueprint } from '../../blueprints/'; /** * Represents an event worth tracking in an analytics system that could inform diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts index 37ffcfa820..36b97f9fff 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppLanguageApi.ts @@ -14,7 +14,23 @@ * limitations under the License. */ -export { - type AppLanguageApi, - appLanguageApiRef, -} from '@backstage/core-plugin-api/alpha'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** @public */ +export type AppLanguageApi = { + getAvailableLanguages(): { languages: string[] }; + + setLanguage(language?: string): void; + + getLanguage(): { language: string }; + + language$(): Observable<{ language: string }>; +}; + +/** + * @public + */ +export const appLanguageApiRef: ApiRef = createApiRef({ + id: 'core.applanguage', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts index ce62cd72c3..e771fad597 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,8 +14,74 @@ * limitations under the License. */ -export { - type AppTheme, - type AppThemeApi, - appThemeApiRef, -} from '@backstage/core-plugin-api'; +import { ReactNode } from 'react'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Describes a theme provided by the app. + * + * @public + */ +export type AppTheme = { + /** + * ID used to remember theme selections. + */ + id: string; + + /** + * Title of the theme + */ + title: string; + + /** + * Theme variant + */ + variant: 'light' | 'dark'; + + /** + * An Icon for the theme mode setting. + */ + icon?: React.ReactElement; + + Provider(props: { children: ReactNode }): JSX.Element | null; +}; + +/** + * The AppThemeApi gives access to the current app theme, and allows switching + * to other options that have been registered as a part of the App. + * + * @public + */ +export type AppThemeApi = { + /** + * Get a list of available themes. + */ + getInstalledThemes(): AppTheme[]; + + /** + * Observe the currently selected theme. A value of undefined means no specific theme has been selected. + */ + activeThemeId$(): Observable; + + /** + * Get the current theme ID. Returns undefined if no specific theme is selected. + */ + getActiveThemeId(): string | undefined; + + /** + * Set a specific theme to use in the app, overriding the default theme selection. + * + * Clear the selection by passing in undefined. + */ + setActiveThemeId(themeId?: string): void; +}; + +/** + * The {@link ApiRef} of {@link AppThemeApi}. + * + * @public + */ +export const appThemeApiRef: ApiRef = createApiRef({ + id: 'core.apptheme', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts index f4fd2f3a6f..89902d2727 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; -import { - FrontendPlugin, - Extension, - ExtensionDataRef, - ExtensionAttachToSpec, -} from '../../wiring'; +import { createApiRef } from '../system'; +import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring'; +import { ExtensionAttachTo } from '../../wiring/resolveExtensionDefinition'; /** * The specification for this {@link AppNode} in the {@link AppTree}. @@ -33,7 +29,7 @@ import { */ export interface AppNodeSpec { readonly id: string; - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; readonly extension: Extension; readonly disabled: boolean; readonly config?: unknown; diff --git a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts index 968a2843ba..d3bada9e46 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts @@ -13,5 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; +import { Config } from '@backstage/config'; -export { type ConfigApi, configApiRef } from '@backstage/core-plugin-api'; +/** + * The Config API is used to provide a mechanism to access the + * runtime configuration of the system. + * + * @public + */ +export type ConfigApi = Config; + +/** + * The {@link ApiRef} of {@link ConfigApi}. + * + * @public + */ +export const configApiRef: ApiRef = createApiRef({ + id: 'core.config', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts index 801ec88c69..85151b05f6 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * A handle for an open dialog that can be used to interact with it. diff --git a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts index 98b3f0bbf0..d23fe3db6c 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -13,5 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; -export { type DiscoveryApi, discoveryApiRef } from '@backstage/core-plugin-api'; +/** + * The discovery API is used to provide a mechanism for plugins to + * discover the endpoint to use to talk to their backend counterpart. + * + * @remarks + * + * The purpose of the discovery API is to allow for many different deployment + * setups and routing methods through a central configuration, instead + * of letting each individual plugin manage that configuration. + * + * Implementations of the discovery API can be a simple as a URL pattern + * using the pluginId, but could also have overrides for individual plugins, + * or query a separate discovery service. + * + * @public + */ +export type DiscoveryApi = { + /** + * Returns the HTTP base backend URL for a given plugin, without a trailing slash. + * + * This method must always be called just before making a request, as opposed to + * fetching the URL when constructing an API client. That is to ensure that more + * flexible routing patterns can be supported. + * + * For example, asking for the URL for `auth` may return something + * like `https://backstage.example.com/api/auth` + */ + getBaseUrl(pluginId: string): Promise; +}; + +/** + * The {@link ApiRef} of {@link DiscoveryApi}. + * + * @public + */ +export const discoveryApiRef: ApiRef = createApiRef({ + id: 'core.discovery', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts index 57782a05dc..9c73d94cac 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/ErrorApi.ts @@ -14,9 +14,78 @@ * limitations under the License. */ -export { - type ErrorApiError, - type ErrorApiErrorContext, - type ErrorApi, - errorApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Mirrors the JavaScript Error class, for the purpose of + * providing documentation and optional fields. + * + * @public + */ +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; + +/** + * Provides additional information about an error that was posted to the application. + * + * @public + */ +export type ErrorApiErrorContext = { + /** + * If set to true, this error should not be displayed to the user. + * + * Hidden errors are typically not displayed in the UI, but the ErrorApi + * implementation may still report them to error tracking services + * or other utilities that care about all errors. + * + * @defaultValue false + */ + hidden?: boolean; +}; + +/** + * The error API is used to report errors to the app, and display them to the user. + * + * @remarks + * + * Plugins can use this API as a method of displaying errors to the user, but also + * to report errors for collection by error reporting services. + * + * If an error can be displayed inline, e.g. as feedback in a form, that should be + * preferred over relying on this API to display the error. The main use of this API + * for displaying errors should be for asynchronous errors, such as a failing background process. + * + * Even if an error is displayed inline, it should still be reported through this API + * if it would be useful to collect or log it for debugging purposes, but with + * the hidden flag set. For example, an error arising from form field validation + * should probably not be reported, while a failed REST call would be useful to report. + * + * @public + */ +export type ErrorApi = { + /** + * Post an error for handling by the application. + */ + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + + /** + * Observe errors posted by other parts of the application. + */ + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; + +/** + * The {@link ApiRef} of {@link ErrorApi}. + * + * @public + */ +export const errorApiRef: ApiRef = createApiRef({ + id: 'core.error', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 820efc0a25..d4429975cc 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,11 +13,114 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type FeatureFlag, - type FeatureFlagState, - type FeatureFlagsSaveOptions, - type FeatureFlagsApi, - featureFlagsApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; + +/** + * Feature flag descriptor. + * + * @public + */ +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; + +/** + * Enum representing the state of a feature flag (inactive/active). + * + * @public + */ +export const FeatureFlagState = { + /** + * Feature flag inactive (disabled). + */ + None: 0, + /** + * Feature flag active (enabled). + */ + Active: 1, +} as const; + +/** + * @public + */ +export type FeatureFlagState = + (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; + +/** + * @public + */ +export namespace FeatureFlagState { + export type None = typeof FeatureFlagState.None; + export type Active = typeof FeatureFlagState.Active; +} + +/** + * Options to use when saving feature flags. + * + * @public + */ +export type FeatureFlagsSaveOptions = { + /** + * The new feature flag states to save. + */ + states: Record; + + /** + * Whether the saves states should be merged into the existing ones, or replace them. + * + * Defaults to false. + */ + merge?: boolean; +}; + +/** + * The feature flags API is used to toggle functionality to users across plugins and Backstage. + * + * @remarks + * + * Plugins can use this API to register feature flags that they have available + * for users to enable/disable, and this API will centralize the current user's + * state of which feature flags they would like to enable. + * + * This is ideal for Backstage plugins, as well as your own App, to trial incomplete + * or unstable upcoming features. Although there will be a common interface for users + * to enable and disable feature flags, this API acts as another way to enable/disable. + * + * @public + */ +export interface FeatureFlagsApi { + /** + * Registers a new feature flag. Once a feature flag has been registered it + * can be toggled by users, and read back to enable or disable features. + */ + registerFlag(flag: FeatureFlag): void; + + /** + * Get a list of all registered flags. + */ + getRegisteredFlags(): FeatureFlag[]; + + /** + * Whether the feature flag with the given name is currently activated for the user. + */ + isActive(name: string): boolean; + + /** + * Save the user's choice of feature flag states. + */ + save(options: FeatureFlagsSaveOptions): void; +} + +/** + * The {@link ApiRef} of {@link FeatureFlagsApi}. + * + * @public + */ +export const featureFlagsApiRef: ApiRef = createApiRef({ + id: 'core.featureflags', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts index bcc9bc6a91..aba0e53bb7 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/FetchApi.ts @@ -14,4 +14,38 @@ * limitations under the License. */ -export { type FetchApi, fetchApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; + +/** + * A wrapper for the fetch API, that has additional behaviors such as the + * ability to automatically inject auth information where necessary. + * + * @public + */ +export type FetchApi = { + /** + * The `fetch` implementation. + */ + fetch: typeof fetch; +}; + +/** + * The {@link ApiRef} of {@link FetchApi}. + * + * @remarks + * + * This is a wrapper for the fetch API, that has additional behaviors such as + * the ability to automatically inject auth information where necessary. + * + * Note that the default behavior of this API (unless overridden by your org), + * is to require that the user is already signed in so that it has auth + * information to inject. Therefore, using the default implementation of this + * utility API e.g. on the `SignInPage` or similar, would cause issues. In + * special circumstances like those, you can use the regular system `fetch` + * instead. + * + * @public + */ +export const fetchApiRef: ApiRef = createApiRef({ + id: 'core.fetch', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts index aa5b7f4fac..fbc9928dc1 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { IconComponent } from '../../icons'; /** diff --git a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts index 03503a8fba..1b127a1971 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/IdentityApi.ts @@ -13,5 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiRef, createApiRef } from '../system'; +import { BackstageUserIdentity, ProfileInfo } from './auth'; -export { type IdentityApi, identityApiRef } from '@backstage/core-plugin-api'; +/** + * The Identity API used to identify and get information about the signed in user. + * + * @public + */ +export type IdentityApi = { + /** + * The profile of the signed in user. + */ + getProfileInfo(): Promise; + + /** + * User identity information within Backstage. + */ + getBackstageIdentity(): Promise; + + /** + * Provides credentials in the form of a token which proves the identity of the signed in user. + * + * The token will be undefined if the signed in user does not have a verified + * identity, such as a demo user or mocked user for e2e tests. + */ + getCredentials(): Promise<{ token?: string }>; + + /** + * Sign out the current user + */ + signOut(): Promise; +}; + +/** + * The {@link ApiRef} of {@link IdentityApi}. + * + * @public + */ +export const identityApiRef: ApiRef = createApiRef({ + id: 'core.identity', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts index ff7a0f2852..75bf3a3864 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,10 +14,118 @@ * limitations under the License. */ -export { - type OAuthRequesterOptions, - type OAuthRequester, - type PendingOAuthRequest, - type OAuthRequestApi, - oauthRequestApiRef, -} from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; +import { ApiRef, createApiRef } from '../system'; +import { AuthProviderInfo } from './auth'; + +/** + * Describes how to handle auth requests. Both how to show them to the user, and what to do when + * the user accesses the auth request. + * + * @public + */ +export type OAuthRequesterOptions = { + /** + * Information about the auth provider, which will be forwarded to auth requests. + */ + provider: AuthProviderInfo; + + /** + * Implementation of the auth flow, which will be called synchronously when + * trigger() is called on an auth requests. + */ + onAuthRequest(scopes: Set): Promise; +}; + +/** + * Function used to trigger new auth requests for a set of scopes. + * + * @remarks + * + * The returned promise will resolve to the same value returned by the onAuthRequest in the + * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the + * union of all requested scopes. + * + * @public + */ +export type OAuthRequester = ( + scopes: Set, +) => Promise; + +/** + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * @remarks + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. + * + * @public + */ +export type PendingOAuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + provider: AuthProviderInfo; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject(): void; + + /** + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. + */ + trigger(): Promise; +}; + +/** + * Provides helpers for implemented OAuth login flows within Backstage. + * + * @public + */ +export type OAuthRequestApi = { + /** + * A utility for showing login popups or similar things, and merging together multiple requests for + * different scopes into one request that includes all scopes. + * + * The passed in options provide information about the login provider, and how to handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + + /** + * Observers pending auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. + */ + authRequest$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link OAuthRequestApi}. + * + * @public + */ +export const oauthRequestApiRef: ApiRef = createApiRef({ + id: 'core.oauthrequest', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts index 6ae25a814e..0c3ca9c4cf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts @@ -20,7 +20,7 @@ import { SubRouteRef, ExternalRouteRef, } from '../../routing'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * TS magic for handling route parameters. diff --git a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts index 2309d2414c..1506e5f143 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/StorageApi.ts @@ -14,8 +14,97 @@ * limitations under the License. */ -export { - type StorageValueSnapshot, - type StorageApi, - storageApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { JsonValue, Observable } from '@backstage/types'; + +/** + * A snapshot in time of the current known value of a storage key. + * + * @public + */ +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; + +/** + * Provides a key-value persistence API. + * + * @public + */ +export interface StorageApi { + /** + * Create a bucket to store data in. + * + * @param name - Namespace for the storage to be stored under, + * will inherit previous namespaces too + */ + forBucket(name: string): StorageApi; + + /** + * Remove persistent data. + * + * @param key - Unique key associated with the data. + */ + remove(key: string): Promise; + + /** + * Save persistent data, and emit messages to anyone that is using + * {@link StorageApi.observe$} for this key. + * + * @param key - Unique key associated with the data. + * @param data - The data to be stored under the key. + */ + set(key: string, data: T): Promise; + + /** + * Observe the value over time for a particular key in the current bucket. + * + * @remarks + * + * The observable will only emit values when the value changes in the underlying + * storage, although multiple values with the same shape may be emitted in a row. + * + * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is + * `'unknown'`, then you are guaranteed to receive a snapshot with a known + * presence, as long as you observe the key within the same tick. + * + * Since the emitted values are shared across all subscribers, it is important + * not to mutate the returned values. The values may be frozen as a precaution. + * + * @param key - Unique key associated with the data + */ + observe$( + key: string, + ): Observable>; + + /** + * Returns an immediate snapshot value for the given key, if possible. + * + * @remarks + * + * Combine with {@link StorageApi.observe$} to get notified of value changes. + * + * Note that this method is synchronous, and some underlying storages may be + * unable to retrieve a value using this method - the result may or may not + * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} + * to be sure to receive an actual value eventually. + */ + snapshot(key: string): StorageValueSnapshot; +} + +/** + * The {@link ApiRef} of {@link StorageApi}. + * + * @public + */ +export const storageApiRef: ApiRef = createApiRef({ + id: 'core.storage', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts index ed2b20ce80..09dff04f43 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts @@ -15,7 +15,7 @@ */ import { SwappableComponentRef } from '../../components'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; /** * API for looking up components based on component refs. diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx similarity index 100% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.test.tsx diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts similarity index 98% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.ts rename to packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts index 5cfb14abb2..2578eaf7c4 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/TranslationApi.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; -import { TranslationRef } from '../../translation/TranslationRef'; +import { TranslationRef } from '../../translation'; import { JSX } from 'react'; /** @@ -305,7 +305,7 @@ type TranslationFunctionOptions< > >; -/** @alpha */ +/** @public */ export type TranslationFunction = CollapsedMessages extends infer IMessages extends { [key in string]: string; @@ -340,11 +340,11 @@ export type TranslationFunction = } : never; -/** @alpha */ +/** @public */ export type TranslationSnapshot = { ready: false } | { ready: true; t: TranslationFunction }; -/** @alpha */ +/** @public */ export type TranslationApi = { getTranslation( translationRef: TranslationRef, @@ -356,7 +356,7 @@ export type TranslationApi = { }; /** - * @alpha + * @public */ export const translationApiRef: ApiRef = createApiRef({ id: 'core.translation', diff --git a/packages/frontend-plugin-api/src/apis/definitions/auth.ts b/packages/frontend-plugin-api/src/apis/definitions/auth.ts index 5a31c1603d..9a0ddcd958 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/auth.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/auth.ts @@ -13,29 +13,496 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* We want to maintain the same information as an enum, so we disable the redeclaration warning */ +/* eslint-disable @typescript-eslint/no-redeclare */ -export { - type BackstageIdentityApi, - type BackstageIdentityResponse, - type BackstageUserIdentity, - type AuthProviderInfo, - type AuthRequestOptions, - type OAuthScope, - type OAuthApi, - type OpenIdConnectApi, - type ProfileInfoApi, - type ProfileInfo, - type SessionApi, - SessionState, - atlassianAuthApiRef, - bitbucketAuthApiRef, - bitbucketServerAuthApiRef, - githubAuthApiRef, - gitlabAuthApiRef, - googleAuthApiRef, - oktaAuthApiRef, - microsoftAuthApiRef, - oneloginAuthApiRef, - vmwareCloudAuthApiRef, - openshiftAuthApiRef, -} from '@backstage/core-plugin-api'; +import { ApiRef, createApiRef } from '../system'; +import { IconComponent } from '../../icons/types'; +import { Observable } from '@backstage/types'; + +/** + * This file contains declarations for common interfaces of auth-related APIs. + * The declarations should be used to signal which type of authentication and + * authorization methods each separate auth provider supports. + * + * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, + * would be declared as follows: + * + * const googleAuthApiRef = createApiRef({ ... }) + */ + +/** + * Information about the auth provider. + * + * @remarks + * + * This information is used both to connect the correct auth provider in the backend, as + * well as displaying the provider to the user. + * + * @public + */ +export type AuthProviderInfo = { + /** + * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. + */ + id: string; + + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + */ + icon: IconComponent; + + /** + * Optional user friendly messaage to display for the auth provider. + */ + message?: string; +}; + +/** + * An array of scopes, or a scope string formatted according to the + * auth provider, which is typically a space separated list. + * + * @remarks + * + * See the documentation for each auth provider for the list of scopes + * supported by each provider. + * + * @public + */ +export type OAuthScope = string | string[]; + +/** + * Configuration of an authentication request. + * + * @public + */ +export type AuthRequestOptions = { + /** + * If this is set to true, the user will not be prompted to log in, + * and an empty response will be returned if there is no existing session. + * + * This can be used to perform a check whether the user is logged in, or if you don't + * want to force a user to be logged in, but provide functionality if they already are. + * + * @defaultValue false + */ + optional?: boolean; + + /** + * If this is set to true, the request will bypass the regular oauth login modal + * and open the login popup directly. + * + * The method must be called synchronously from a user action for this to work in all browsers. + * + * @defaultValue false + */ + instantPopup?: boolean; +}; + +/** + * This API provides access to OAuth 2 credentials. It lets you request access tokens, + * which can be used to act on behalf of the user when talking to APIs. + * + * @public + */ +export type OAuthApi = { + /** + * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows + * you to make requests on behalf of the user, and the copes may grant you broader access, depending + * on the auth provider. + * + * Each auth provider has separate handling of scope, so you need to look at the documentation + * for each one to know what scope you need to request. + * + * This method is cheap and should be called each time an access token is used. Do not for example + * store the access token in React component state, as that could cause the token to expire. Instead + * fetch a new access token for each request. + * + * Be sure to include all required scopes when requesting an access token. When testing your implementation + * it is best to log out the Backstage session and then visit your plugin page directly, as + * you might already have some required scopes in your existing session. Not requesting the correct + * scopes can lead to 403 or other authorization errors, which can be tricky to debug. + * + * If the user has not yet granted access to the provider and the set of requested scopes, the user + * will be prompted to log in. The returned promise will not resolve until the user has + * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. + */ + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * This API provides access to OpenID Connect credentials. It lets you request ID tokens, + * which can be passed to backend services to prove the user's identity. + * + * @public + */ +export type OpenIdConnectApi = { + /** + * Requests an OpenID Connect ID Token. + * + * This method is cheap and should be called each time an ID token is used. Do not for example + * store the id token in React component state, as that could cause the token to expire. Instead + * fetch a new id token for each request. + * + * If the user has not yet logged in to Google inside Backstage, the user will be prompted + * to log in. The returned promise will not resolve until the user has successfully logged in. + * The returned promise can be rejected, but only if the user rejects the login request. + */ + getIdToken(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to profile information of the user from an auth provider. + * + * @public + */ +export type ProfileInfoApi = { + /** + * Get profile information for the user as supplied by this auth provider. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getProfile(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to the user's identity within Backstage. + * + * @remarks + * + * An auth provider that implements this interface can be used to sign-in to backstage. It is + * not intended to be used directly from a plugin, but instead serves as a connection between + * this authentication method and the app's {@link IdentityApi} + * + * @public + */ +export type BackstageIdentityApi = { + /** + * Get the user's identity within Backstage. This should normally not be called directly, + * use the {@link IdentityApi} instead. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * User identity information within Backstage. + * + * @public + */ +export type BackstageUserIdentity = { + /** + * The type of identity that this structure represents. In the frontend app + * this will currently always be 'user'. + */ + type: 'user'; + + /** + * The entityRef of the user in the catalog. + * For example User:default/sandra + */ + userEntityRef: string; + + /** + * The user and group entities that the user claims ownership through + */ + ownershipEntityRefs: string[]; +}; + +/** + * Token and Identity response, with the users claims in the Identity. + * + * @public + */ +export type BackstageIdentityResponse = { + /** + * The token used to authenticate the user within Backstage. + */ + token: string; + + /** + * The time at which the token expires. If not set, it can be assumed that the token does not expire. + */ + expiresAt?: Date; + + /** + * Identity information derived from the token. + */ + identity: BackstageUserIdentity; +}; + +/** + * Profile information of the user. + * + * @public + */ +export type ProfileInfo = { + /** + * Email ID. + */ + email?: string; + + /** + * Display name that can be presented to the user. + */ + displayName?: string; + + /** + * URL to an avatar image of the user. + */ + picture?: string; +}; + +/** + * Session state values passed to subscribers of the SessionApi. + * + * @public + */ +export const SessionState = { + /** + * User signed in. + */ + SignedIn: 'SignedIn', + /** + * User not signed in. + */ + SignedOut: 'SignedOut', +} as const; + +/** + * @public + */ +export type SessionState = (typeof SessionState)[keyof typeof SessionState]; + +/** + * @public + */ +export namespace SessionState { + export type SignedIn = typeof SessionState.SignedIn; + export type SignedOut = typeof SessionState.SignedOut; +} + +/** + * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. + * + * @public + */ +export type SessionApi = { + /** + * Sign in with a minimum set of permissions. + */ + signIn(): Promise; + + /** + * Sign out from the current session. This will reload the page. + */ + signOut(): Promise; + + /** + * Observe the current state of the auth session. Emits the current state on subscription. + */ + sessionState$(): Observable; +}; + +/** + * Provides authentication towards Google APIs and identities. + * + * @public + * @remarks + * + * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. + * + * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, + * email and expiration information. Do not rely on any other fields, as they might not be present. + */ +export const googleAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.google', +}); + +/** + * Provides authentication towards GitHub APIs. + * + * @public + * @remarks + * + * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} + * for a full list of supported scopes. + */ +export const githubAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.github', +}); + +/** + * Provides authentication towards Okta APIs. + * + * @public + * @remarks + * + * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} + * for a full list of supported scopes. + */ +export const oktaAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.okta', +}); + +/** + * Provides authentication towards GitLab APIs. + * + * @public + * @remarks + * + * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} + * for a full list of supported scopes. + */ +export const gitlabAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.gitlab', +}); + +/** + * Provides authentication towards Microsoft APIs and identities. + * + * @public + * @remarks + * + * For more info and a full list of supported scopes, see: + * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} + * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} + */ +export const microsoftAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.microsoft', +}); + +/** + * Provides authentication towards OneLogin APIs. + * + * @public + */ +export const oneloginAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.onelogin', +}); + +/** + * Provides authentication towards Bitbucket APIs. + * + * @public + * @remarks + * + * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} + * for a full list of supported scopes. + */ +export const bitbucketAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket', +}); + +/** + * Provides authentication towards Bitbucket Server APIs. + * + * @public + * @remarks + * + * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} + * for a full list of supported scopes. + */ +export const bitbucketServerAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket-server', +}); + +/** + * Provides authentication towards Atlassian APIs. + * + * @public + * @remarks + * + * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} + * for a full list of supported scopes. + */ +export const atlassianAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.atlassian', +}); + +/** + * Provides authentication towards VMware Cloud APIs and identities. + * + * @public + * @remarks + * + * For more info about VMware Cloud identity and access management: + * - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html} + */ +export const vmwareCloudAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.vmware-cloud', +}); + +/** + * Provides authentication towards OpenShift APIs and identities. + * + * @public + * @remarks + * + * See {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/authentication_and_authorization/configuring-oauth-clients} + * on how to configure the OAuth clients and + * {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth} + * for available scopes. + */ +export const openshiftAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.openshift', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 7533481d01..5e7f713fcf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -33,6 +33,7 @@ export { export * from './auth'; export * from './AlertApi'; +export * from './AppLanguageApi'; export * from './AppThemeApi'; export * from './SwappableComponentsApi'; export * from './ConfigApi'; @@ -47,3 +48,4 @@ export * from './OAuthRequestApi'; export * from './RouteResolutionApi'; export * from './StorageApi'; export * from './AnalyticsApi'; +export * from './TranslationApi'; diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts new file mode 100644 index 0000000000..dab872236f --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts @@ -0,0 +1,50 @@ +/* + * 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 { createApiRef } from './ApiRef'; + +describe('ApiRef', () => { + it('should be created', () => { + const ref = createApiRef({ id: 'abc' }); + expect(ref.id).toBe('abc'); + expect(String(ref)).toBe('apiRef{abc}'); + expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); + }); + + it('should reject invalid ids', () => { + for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { + expect(createApiRef({ id }).id).toBe(id); + } + + for (const id of [ + '123', + 'ab-3', + 'ab_c', + '.', + '2ac', + 'ab.3a', + '.abc', + 'abc.', + 'ab..s', + '', + '_', + ]) { + expect(() => createApiRef({ id }).id).toThrow( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, + ); + } + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts index 0d1f8312cb..adedff9f73 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts @@ -14,8 +14,51 @@ * limitations under the License. */ -export { - type ApiRef, - type ApiRefConfig, - createApiRef, -} from '@backstage/core-plugin-api'; +import type { ApiRef } from './types'; + +/** + * API reference configuration - holds an ID of the referenced API. + * + * @public + */ +export type ApiRefConfig = { + id: string; +}; + +class ApiRefImpl implements ApiRef { + constructor(private readonly config: ApiRefConfig) { + const valid = config.id + .split('.') + .flatMap(part => part.split('-')) + .every(part => part.match(/^[a-z][a-z0-9]*$/)); + if (!valid) { + throw new Error( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, + ); + } + } + + get id(): string { + return this.config.id; + } + + // Utility for getting type of an api, using `typeof apiRef.T` + get T(): T { + throw new Error(`tried to read ApiRef.T of ${this}`); + } + + toString() { + return `apiRef{${this.config.id}}`; + } +} + +/** + * Creates a reference to an API. + * + * @param config - The descriptor of the API to reference. + * @returns An API reference. + * @public + */ +export function createApiRef(config: ApiRefConfig): ApiRef { + return new ApiRefImpl(config); +} diff --git a/packages/frontend-plugin-api/src/apis/system/helpers.ts b/packages/frontend-plugin-api/src/apis/system/helpers.ts index 5de265be99..eaffed3003 100644 --- a/packages/frontend-plugin-api/src/apis/system/helpers.ts +++ b/packages/frontend-plugin-api/src/apis/system/helpers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,61 @@ * limitations under the License. */ -export { createApiFactory } from '@backstage/core-plugin-api'; +import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; + +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * This function doesn't actually do anything, it's only used to infer types. + * + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>(factory: ApiFactory): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @param api - Ref of the API that will be produced by the factory. + * @param instance - Implementation of the API to use. + * @public + */ +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; +/** + * Used to infer types for a standalone {@link ApiFactory} that isn't immediately passed + * to another function. + * + * @remarks + * + * Creates factory from {@link ApiRef} or returns the factory itself if provided. + * + * @param factory - Existing factory or {@link ApiRef}. + * @param instance - The instance to be returned by the factory. + * @public + */ +export function createApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>( + factory: ApiFactory | ApiRef, + instance?: Impl, +): ApiFactory { + if ('id' in factory) { + return { + api: factory, + deps: {} as TypesToApiRefs, + factory: () => instance!, + }; + } + return factory; +} diff --git a/packages/frontend-plugin-api/src/apis/system/index.ts b/packages/frontend-plugin-api/src/apis/system/index.ts index b1bd3ac2b3..964750474e 100644 --- a/packages/frontend-plugin-api/src/apis/system/index.ts +++ b/packages/frontend-plugin-api/src/apis/system/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/frontend-plugin-api/src/apis/system/types.ts b/packages/frontend-plugin-api/src/apis/system/types.ts index cc44e19e3f..96614c320c 100644 --- a/packages/frontend-plugin-api/src/apis/system/types.ts +++ b/packages/frontend-plugin-api/src/apis/system/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,61 @@ * limitations under the License. */ -export type { - ApiRef, - AnyApiRef, - TypesToApiRefs, - ApiHolder, - ApiFactory, - AnyApiFactory, -} from '@backstage/core-plugin-api'; +/** + * API reference. + * + * @public + */ +export type ApiRef = { + id: string; + T: T; +}; + +/** + * Catch-all {@link ApiRef} type. + * + * @public + */ +export type AnyApiRef = ApiRef; + +/** + * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. + * + * @public + */ +export type TypesToApiRefs = { [key in keyof T]: ApiRef }; + +/** + * Provides lookup of APIs through their {@link ApiRef}s. + * + * @public + */ +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; + +/** + * Describes type returning API implementations. + * + * @public + */ +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; + +/** + * Catch-all {@link ApiFactory} type. + * + * @public + */ +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { [key in string]: unknown } +>; diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx new file mode 100644 index 0000000000..4b9d80fb62 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx @@ -0,0 +1,40 @@ +/* + * 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 { renderHook } from '@testing-library/react'; +import { createVersionedContextForTesting } from '@backstage/version-bridge'; +import { createApiRef } from './ApiRef'; +import { useApi } from './useApi'; + +describe('useApi', () => { + const context = createVersionedContextForTesting('api-context'); + + afterEach(() => { + context.reset(); + }); + + it('should resolve routes', () => { + const get = jest.fn(() => 'my-api-impl'); + context.set({ 1: { get } }); + + const apiRef = createApiRef({ id: 'x' }); + const renderedHook = renderHook(() => useApi(apiRef)); + + const value = renderedHook.result.current; + expect(value).toBe('my-api-impl'); + expect(get).toHaveBeenCalledWith(apiRef); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.tsx index d2efa49fa1..30e39033f7 100644 --- a/packages/frontend-plugin-api/src/apis/system/useApi.tsx +++ b/packages/frontend-plugin-api/src/apis/system/useApi.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,81 @@ * limitations under the License. */ -export { useApiHolder, useApi, withApis } from '@backstage/core-plugin-api'; +import { ComponentType, PropsWithChildren } from 'react'; +import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; +import { useVersionedContext } from '@backstage/version-bridge'; +import { NotImplementedError } from '@backstage/errors'; + +/** + * React hook for retrieving {@link ApiHolder}, an API catalog. + * + * @public + */ +export function useApiHolder(): ApiHolder { + const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); + if (!versionedHolder) { + throw new NotImplementedError('API context is not available'); + } + + const apiHolder = versionedHolder.atVersion(1); + if (!apiHolder) { + throw new NotImplementedError('ApiContext v1 not available'); + } + return apiHolder; +} + +/** + * React hook for retrieving APIs. + * + * @param apiRef - Reference of the API to use. + * @public + */ +export function useApi(apiRef: ApiRef): T { + const apiHolder = useApiHolder(); + + const api = apiHolder.get(apiRef); + if (!api) { + throw new NotImplementedError(`No implementation available for ${apiRef}`); + } + return api; +} + +/** + * Wrapper for giving component an API context. + * + * @param apis - APIs for the context. + * @public + */ +export function withApis(apis: TypesToApiRefs) { + return function withApisWrapper( + WrappedComponent: ComponentType, + ) { + const Hoc = (props: PropsWithChildren>) => { + const apiHolder = useApiHolder(); + + const impls = {} as T; + + for (const key in apis) { + if (apis.hasOwnProperty(key)) { + const ref = apis[key]; + + const api = apiHolder.get(ref); + if (!api) { + throw new NotImplementedError( + `No implementation available for ${ref}`, + ); + } + impls[key] = api; + } + } + + return ; + }; + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + Hoc.displayName = `withApis(${displayName})`; + + return Hoc; + }; +} diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 471e933b12..6d16a215e1 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -16,7 +16,7 @@ import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../apis/system'; describe('ApiBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -203,10 +203,16 @@ describe('ApiBlueprint', () => { "optional": false, "singleton": false, }, + "context": { + "input": "test", + "kind": "api", + "name": "test", + }, "extensionData": [ [Function], ], "replaces": undefined, + "withContext": [Function], }, }, "kind": "api", diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx index 39f03cfad4..31d7323aec 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.test.tsx @@ -14,7 +14,19 @@ * limitations under the License. */ +import { screen, waitFor } from '@testing-library/react'; +import { + MockErrorApi, + TestApiProvider, + withLogCollector, +} from '@backstage/test-utils'; +import { errorApiRef } from '../apis'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { AppRootElementBlueprint } from './AppRootElementBlueprint'; +import { ForwardedError } from '@backstage/errors'; describe('AppRootElementBlueprint', () => { it('should create an extension with sensible defaults', () => { @@ -46,4 +58,57 @@ describe('AppRootElementBlueprint', () => { } `); }); + + it('should post error to errorApi and not render children when error occurs', async () => { + const errorApi = new MockErrorApi({ collect: true }); + const errorMessage = 'Test error message'; + const ErrorComponent = () => { + throw new Error(errorMessage); + }; + + await withLogCollector(['error'], async () => { + const extension = AppRootElementBlueprint.make({ + params: { + element: , + }, + }); + + const tester = createExtensionTester(extension); + renderInTestApp( + + {tester.reactElement()} + , + ); + + await waitFor(() => { + const errors = errorApi.getErrors(); + expect(errors.length).toBeGreaterThan(0); + const postedError = errors[0].error; + expect(postedError).toBeInstanceOf(ForwardedError); + expect(postedError.message).toBe( + "Error in extension 'app-root-element:test'; caused by Error: Test error message", + ); + }); + + expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + }); + }); + + it('should render children when there is no error', async () => { + const successMessage = 'Success!'; + const SuccessComponent = () =>

    {successMessage}
    ; + + const extension = AppRootElementBlueprint.make({ + params: { + element: , + }, + }); + + const tester = createExtensionTester(extension); + renderInTestApp(tester.reactElement()); + + await waitFor(() => { + expect(screen.getByText(successMessage)).toBeInTheDocument(); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx similarity index 78% rename from packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts rename to packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx index d1c71155f2..955abce136 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ExtensionBoundary } from '@backstage/frontend-plugin-api'; import { coreExtensionData, createExtensionBlueprint } from '../wiring'; /** @@ -26,7 +27,11 @@ export const AppRootElementBlueprint = createExtensionBlueprint({ kind: 'app-root-element', attachTo: { id: 'app/root', input: 'elements' }, output: [coreExtensionData.reactElement], - *factory(params: { element: JSX.Element }) { - yield coreExtensionData.reactElement(params.element); + *factory(params: { element: JSX.Element }, { node }) { + yield coreExtensionData.reactElement( + + {params.element} + , + ); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index 197f72da73..ce59a9439b 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '../icons/types'; import { RouteRef } from '../routing'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index 5e4112a8d1..b28a0108ca 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -14,10 +14,27 @@ * limitations under the License. */ -import { ComponentType, lazy } from 'react'; +import { ComponentType, lazy, ReactNode } from 'react'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -import { SignInPageProps } from '@backstage/core-plugin-api'; import { ExtensionBoundary } from '../components'; +import { IdentityApi } from '../apis'; + +/** + * Props for the `SignInPage` component. + * + * @public + */ +export type SignInPageProps = { + /** + * Set the IdentityApi on successful sign-in. This should only be called once. + */ + onSignInSuccess(identityApi: IdentityApi): void; + + /** + * The children to render. + */ + children?: ReactNode; +}; const componentDataRef = createExtensionDataRef< ComponentType diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 5ec164fa8b..7a53f28fa3 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; +import { AppTheme } from '../apis/definitions/AppThemeApi'; import { ThemeBlueprint } from './ThemeBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index 323078bc4e..aaf4f32608 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AppTheme } from '@backstage/core-plugin-api'; +import { AppTheme } from '../apis/definitions/AppThemeApi'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; const themeDataRef = createExtensionDataRef().with({ diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index c8699b4232..379c8d237b 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -30,7 +30,10 @@ export { export { NavItemBlueprint } from './NavItemBlueprint'; export { PageBlueprint } from './PageBlueprint'; export { RouterBlueprint } from './RouterBlueprint'; -export { SignInPageBlueprint } from './SignInPageBlueprint'; +export { + type SignInPageProps, + SignInPageBlueprint, +} from './SignInPageBlueprint'; export { ThemeBlueprint } from './ThemeBlueprint'; export { TranslationBlueprint } from './TranslationBlueprint'; export { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; diff --git a/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx new file mode 100644 index 0000000000..e195da5d0f --- /dev/null +++ b/packages/frontend-plugin-api/src/components/ErrorApiBoundary.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, ErrorInfo, ReactNode } from 'react'; +import { AppNode, ErrorApi } from '../apis'; +import { ForwardedError } from '@backstage/errors'; + +/** @internal */ +export class ErrorApiBoundary extends Component< + { + children: ReactNode; + node: AppNode; + errorApi?: ErrorApi; + }, + { error?: Error } +> { + static getDerivedStateFromError(error: Error) { + return { error }; + } + + state = { error: undefined }; + + componentDidCatch(error: Error, _errorInfo: ErrorInfo) { + const { node, errorApi } = this.props; + errorApi?.post( + new ForwardedError(`Error in extension '${node.spec.id}'`, error), + ); + } + + render() { + if (this.state.error) { + return null; + } + + return this.props.children; + } +} diff --git a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx similarity index 80% rename from packages/frontend-plugin-api/src/components/ErrorBoundary.tsx rename to packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx index 4cde84ee93..1584f18514 100644 --- a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ErrorDisplayBoundary.tsx @@ -14,25 +14,23 @@ * limitations under the License. */ -import { Component, PropsWithChildren } from 'react'; +import { Component, ReactNode } from 'react'; import { FrontendPlugin } from '../wiring'; import { ErrorDisplay } from './DefaultSwappableComponents'; -type ErrorBoundaryProps = PropsWithChildren<{ - plugin?: FrontendPlugin; -}>; -type ErrorBoundaryState = { error?: Error }; - /** @internal */ -export class ErrorBoundary extends Component< - ErrorBoundaryProps, - ErrorBoundaryState +export class ErrorDisplayBoundary extends Component< + { + children: ReactNode; + plugin: FrontendPlugin; + }, + { error?: Error } > { static getDerivedStateFromError(error: Error) { return { error }; } - state: ErrorBoundaryState = { error: undefined }; + state = { error: undefined }; handleErrorReset = () => { this.setState({ error: undefined }); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index 6cce114d4d..ae741f8922 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -16,14 +16,11 @@ import { useEffect } from 'react'; import { act, screen, waitFor } from '@testing-library/react'; -import { - mockApis, - TestApiProvider, - withLogCollector, -} from '@backstage/test-utils'; +import { TestApiProvider, withLogCollector } from '@backstage/test-utils'; import { ExtensionBoundary } from './ExtensionBoundary'; import { coreExtensionData, createExtension } from '../wiring'; -import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { analyticsApiRef } from '../apis/definitions/AnalyticsApi'; +import { useAnalytics } from '../analytics'; import { createRouteRef } from '../routing'; import { createExtensionTester, @@ -93,7 +90,7 @@ describe('ExtensionBoundary', () => { it('should wrap children with analytics context', async () => { const action = 'render'; const subject = 'analytics'; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; const AnalyticsComponent = () => { const analytics = useAnalytics(); @@ -134,7 +131,7 @@ describe('ExtensionBoundary', () => { }); return null; }; - const analyticsApiMock = mockApis.analytics(); + const analyticsApiMock = { captureEvent: jest.fn() }; await act(async () => { renderInTestApp( diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 848ea64a2c..6e4487ed9b 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -21,15 +21,24 @@ import { useEffect, lazy as reactLazy, } from 'react'; -import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; -import { ErrorBoundary } from './ErrorBoundary'; +import { AnalyticsContext, useAnalytics } from '../analytics'; +import { ErrorDisplayBoundary } from './ErrorDisplayBoundary'; +import { ErrorApiBoundary } from './ErrorApiBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; -import { AppNode } from '../apis'; +import { AppNode, ErrorApi, errorApiRef, useApi } from '../apis'; import { coreExtensionData } from '../wiring'; import { AppNodeProvider } from './AppNodeProvider'; import { Progress } from './DefaultSwappableComponents'; +function useOptionalErrorApi(): ErrorApi | undefined { + try { + return useApi(errorApiRef); + } catch { + return undefined; + } +} + type RouteTrackerProps = PropsWithChildren<{ enabled?: boolean; }>; @@ -53,6 +62,7 @@ const RouteTracker = (props: RouteTrackerProps) => { /** @public */ export interface ExtensionBoundaryProps { + errorPresentation?: 'error-api' | 'error-display'; node: AppNode; children: ReactNode; } @@ -61,6 +71,8 @@ export interface ExtensionBoundaryProps { export function ExtensionBoundary(props: ExtensionBoundaryProps) { const { node, children } = props; + const errorApi = useOptionalErrorApi(); + const hasRoutePathOutput = Boolean( node.instance?.getData(coreExtensionData.routePath), ); @@ -70,18 +82,30 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { extensionId: node.spec.id, - pluginId: node.spec.plugin?.id ?? 'app', + pluginId: plugin.id ?? 'app', }; + let content = ( + + {children} + + ); + + if (props.errorPresentation === 'error-api') { + content = ( + + {content} + + ); + } else { + content = ( + {content} + ); + } + return ( - }> - - - {children} - - - + }>{content} ); } diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx index 91aaeb2a25..ee1136fca3 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.test.tsx @@ -31,7 +31,7 @@ describe('ExtensionSuspense', () => { ), ); - expect(screen.getByTestId('progress')).toBeInTheDocument(); + expect(screen.getByTestId('core-progress')).toBeInTheDocument(); }); it('should render the lazy loaded children component', async () => { diff --git a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx index 0ae8b413e4..9439cafed8 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionSuspense.tsx @@ -15,7 +15,7 @@ */ import { ReactNode, Suspense } from 'react'; -import { useApp } from '@backstage/core-plugin-api'; +import { Progress } from './DefaultSwappableComponents'; /** @public */ export interface ExtensionSuspenseProps { @@ -26,8 +26,5 @@ export interface ExtensionSuspenseProps { export function ExtensionSuspense(props: ExtensionSuspenseProps) { const { children } = props; - const app = useApp(); - const { Progress } = app.getComponents(); - return }>{children}; } diff --git a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts index 97d3dd2264..15366c8a7a 100644 --- a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.test.ts @@ -14,24 +14,23 @@ * limitations under the License. */ -import { - ExternalRouteRef, - createExternalRouteRef, - toInternalExternalRouteRef, -} from './ExternalRouteRef'; +import { ExternalRouteRef, createExternalRouteRef } from './ExternalRouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; import { AnyRouteRefParams } from './types'; describe('ExternalRouteRef', () => { it('should be created', () => { const routeRef: ExternalRouteRef = createExternalRouteRef(); - const internal = toInternalExternalRouteRef(routeRef); + const internal = OpaqueExternalRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual([]); expect(String(internal)).toMatch( - /^ExternalRouteRef\{created at '.*ExternalRouteRef\.test\.ts.*'\}$/, + /^externalRouteRef\{id=undefined,at='.*ExternalRouteRef\.test\.ts.*'\}$/, ); internal.setId('some-id'); - expect(String(internal)).toBe('ExternalRouteRef{some-id}'); + expect(String(internal)).toMatch( + /^externalRouteRef\{id=some-id,at='.*ExternalRouteRef\.test\.ts.*'\}$/, + ); }); it('should be created with params', () => { @@ -39,7 +38,7 @@ describe('ExternalRouteRef', () => { x: string; y: string; }> = createExternalRouteRef({ params: ['x', 'y'] }); - const internal = toInternalExternalRouteRef(routeRef); + const internal = OpaqueExternalRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual(['x', 'y']); }); diff --git a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts index 3de276f85c..cd2f98f504 100644 --- a/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/ExternalRouteRef.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { RouteRefImpl } from './RouteRef'; +import { OpaqueExternalRouteRef } from '@internal/frontend'; import { describeParentCallSite } from './describeParentCallSite'; import { AnyRouteRefParams } from './types'; @@ -58,37 +58,6 @@ export function toInternalExternalRouteRef< return r; } -/** @internal */ -export function isExternalRouteRef(opaque: { - $$type: string; -}): opaque is ExternalRouteRef { - return opaque.$$type === '@backstage/ExternalRouteRef'; -} - -/** @internal */ -class ExternalRouteRefImpl - extends RouteRefImpl - implements InternalExternalRouteRef -{ - readonly $$type = '@backstage/ExternalRouteRef' as any; - readonly params: string[]; - readonly defaultTarget: string | undefined; - - constructor( - params: string[] = [], - defaultTarget: string | undefined, - creationSite: string, - ) { - super(params, creationSite); - this.params = params; - this.defaultTarget = defaultTarget; - } - - getDefaultTarget() { - return this.defaultTarget; - } -} - /** * Creates a route descriptor, to be later bound to a concrete route by the app. Used to implement cross-plugin route references. * @@ -102,7 +71,7 @@ class ExternalRouteRefImpl export function createExternalRouteRef< TParams extends { [param in TParamKeys]: string } | undefined = undefined, TParamKeys extends string = string, ->(options?: { +>(config?: { /** * The parameters that will be provided to the external route reference. */ @@ -124,9 +93,38 @@ export function createExternalRouteRef< ? TParams : { [param in TParamKeys]: string } > { - return new ExternalRouteRefImpl( - options?.params as string[] | undefined, - options?.defaultTarget, - describeParentCallSite(), - ); + const params = (config?.params ?? []) as string[]; + const creationSite = describeParentCallSite(); + + let id: string | undefined = undefined; + + return OpaqueExternalRouteRef.createInstance('v1', { + T: undefined as unknown as TParams, + getParams() { + return params; + }, + getDescription() { + if (id) { + return id; + } + return `created at '${creationSite}'`; + }, + getDefaultTarget() { + return config?.defaultTarget; + }, + setId(newId: string) { + if (!newId) { + throw new Error(`ExternalRouteRef id must be a non-empty string`); + } + if (id && id !== newId) { + throw new Error( + `ExternalRouteRef was referenced twice as both '${id}' and '${newId}'`, + ); + } + id = newId; + }, + toString(): string { + return `externalRouteRef{id=${id},at='${creationSite}'}`; + }, + }); } diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts index 570723df9a..1bd546f821 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts @@ -15,18 +15,19 @@ */ import { AnyRouteRefParams } from './types'; -import { RouteRef, createRouteRef, toInternalRouteRef } from './RouteRef'; +import { RouteRef, createRouteRef } from './RouteRef'; +import { OpaqueRouteRef } from '@internal/frontend'; describe('RouteRef', () => { it('should be created and have a mutable ID', () => { const routeRef: RouteRef = createRouteRef(); - const internal = toInternalRouteRef(routeRef); + const internal = OpaqueRouteRef.toInternal(routeRef); expect(internal.T).toBe(undefined); expect(internal.getParams()).toEqual([]); expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); expect(String(internal)).toMatch( - /^RouteRef\{created at .*RouteRef\.test\.ts.*\}$/, + /^routeRef\{id=undefined,at='.*RouteRef\.test\.ts.*'\}$/, ); expect(() => internal.setId('')).toThrow( @@ -34,7 +35,9 @@ describe('RouteRef', () => { ); internal.setId('some-id'); - expect(String(internal)).toBe('RouteRef{some-id}'); + expect(String(internal)).toMatch( + /^routeRef\{id=some-id,at='.*RouteRef\.test\.ts.*'\}$/, + ); internal.setId('some-id'); // Should allow same ID expect(() => internal.setId('some-other-id')).toThrow( @@ -49,7 +52,7 @@ describe('RouteRef', () => { }> = createRouteRef({ params: ['x', 'y'], }); - const internal = toInternalRouteRef(routeRef); + const internal = OpaqueRouteRef.toInternal(routeRef); expect(internal.getParams()).toEqual(['x', 'y']); expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); }); diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.ts b/packages/frontend-plugin-api/src/routing/RouteRef.ts index 46076e6258..62f29adb86 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { OpaqueRouteRef } from '@internal/frontend'; import { describeParentCallSite } from './describeParentCallSite'; import { AnyRouteRefParams } from './types'; @@ -33,93 +34,6 @@ export interface RouteRef< readonly T: TParams; } -/** @internal */ -export interface InternalRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, -> extends RouteRef { - readonly version: 'v1'; - getParams(): string[]; - getDescription(): string; - - alias: string | undefined; - - setId(id: string): void; -} - -/** @internal */ -export function toInternalRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, ->(resource: RouteRef): InternalRouteRef { - const r = resource as InternalRouteRef; - if (r.$$type !== '@backstage/RouteRef') { - throw new Error(`Invalid RouteRef, bad type '${r.$$type}'`); - } - - return r; -} - -/** @internal */ -export function isRouteRef(opaque: { $$type: string }): opaque is RouteRef { - return opaque.$$type === '@backstage/RouteRef'; -} - -/** @internal */ -export class RouteRefImpl implements InternalRouteRef { - readonly $$type = '@backstage/RouteRef'; - readonly version = 'v1'; - declare readonly T: never; - - #id?: string; - readonly #params: string[]; - readonly #creationSite: string; - readonly #alias?: string; - - constructor( - readonly params: string[] = [], - creationSite: string, - alias?: string, - ) { - this.#params = params; - this.#creationSite = creationSite; - this.#alias = alias; - } - - getParams(): string[] { - return this.#params; - } - - get alias(): string | undefined { - return this.#alias; - } - - getDescription(): string { - if (this.#id) { - return this.#id; - } - return `created at '${this.#creationSite}'`; - } - - get #name() { - return this.$$type.slice('@backstage/'.length); - } - - setId(id: string): void { - if (!id) { - throw new Error(`${this.#name} id must be a non-empty string`); - } - if (this.#id && this.#id !== id) { - throw new Error( - `${this.#name} was referenced twice as both '${this.#id}' and '${id}'`, - ); - } - this.#id = id; - } - - toString(): string { - return `${this.#name}{${this.getDescription()}}`; - } -} - /** * Create a {@link RouteRef} from a route descriptor. * @@ -145,9 +59,36 @@ export function createRouteRef< ? TParams : { [param in TParamKeys]: string } > { - return new RouteRefImpl( - config?.params as string[] | undefined, - describeParentCallSite(), - config?.aliasFor, - ) as RouteRef; + const params = (config?.params ?? []) as string[]; + const creationSite = describeParentCallSite(); + + let id: string | undefined = undefined; + + return OpaqueRouteRef.createInstance('v1', { + T: undefined as unknown as TParams, + getParams() { + return params; + }, + getDescription() { + if (id) { + return id; + } + return `created at '${creationSite}'`; + }, + alias: config?.aliasFor, + setId(newId: string) { + if (!newId) { + throw new Error(`RouteRef id must be a non-empty string`); + } + if (id && id !== newId) { + throw new Error( + `RouteRef was referenced twice as both '${id}' and '${newId}'`, + ); + } + id = newId; + }, + toString(): string { + return `routeRef{id=${id},at='${creationSite}'}`; + }, + }); } diff --git a/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts b/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts index b04728c49b..4746600f40 100644 --- a/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/SubRouteRef.test.ts @@ -15,33 +15,32 @@ */ import { AnyRouteRefParams } from './types'; -import { - SubRouteRef, - createSubRouteRef, - toInternalSubRouteRef, -} from './SubRouteRef'; -import { createRouteRef, toInternalRouteRef } from './RouteRef'; +import { SubRouteRef, createSubRouteRef } from './SubRouteRef'; +import { createRouteRef } from './RouteRef'; +import { OpaqueRouteRef, OpaqueSubRouteRef } from '@internal/frontend'; const parent = createRouteRef(); const parentX = createRouteRef({ params: ['x'] }); describe('SubRouteRef', () => { it('should be created', () => { - const internalParent = toInternalRouteRef(createRouteRef()); + const internalParent = OpaqueRouteRef.toInternal(createRouteRef()); const routeRef: SubRouteRef = createSubRouteRef({ parent: internalParent, path: '/foo', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo'); expect(internal.T).toBe(undefined); expect(internal.getParent()).toBe(internalParent); expect(internal.getParams()).toEqual([]); expect(String(internal)).toMatch( - /SubRouteRef\{at \/foo with parent created at '.*SubRouteRef\.test\.ts.*'\}/, + /^subRouteRef\{path='\/foo',parent=routeRef\{id=undefined,at='.*SubRouteRef\.test\.ts.*'\}\}$/, ); internalParent.setId('some-id'); - expect(String(internal)).toBe('SubRouteRef{at /foo with parent some-id}'); + expect(String(internal)).toMatch( + /^subRouteRef\{path='\/foo',parent=routeRef\{id=some-id,at='.*SubRouteRef\.test\.ts.*'\}\}$/, + ); }); it('should be created with params', () => { @@ -49,7 +48,7 @@ describe('SubRouteRef', () => { parent, path: '/foo/:bar', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/:bar'); expect(internal.getParent()).toBe(parent); expect(internal.getParams()).toEqual(['bar']); @@ -64,7 +63,7 @@ describe('SubRouteRef', () => { parent: parentX, path: '/foo/:y/:z', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/:y/:z'); expect(internal.getParent()).toBe(parentX); expect(internal.getParams()).toEqual(['x', 'y', 'z']); @@ -75,7 +74,7 @@ describe('SubRouteRef', () => { parent: parentX, path: '/foo/bar', }); - const internal = toInternalSubRouteRef(routeRef); + const internal = OpaqueSubRouteRef.toInternal(routeRef); expect(internal.path).toBe('/foo/bar'); expect(internal.getParent()).toBe(parentX); expect(internal.getParams()).toEqual(['x']); diff --git a/packages/frontend-plugin-api/src/routing/SubRouteRef.ts b/packages/frontend-plugin-api/src/routing/SubRouteRef.ts index 74996ab4e7..4fe11ff6fb 100644 --- a/packages/frontend-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/SubRouteRef.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { RouteRef, toInternalRouteRef } from './RouteRef'; +import { OpaqueRouteRef, OpaqueSubRouteRef } from '@internal/frontend'; +import { RouteRef } from './RouteRef'; import { AnyRouteRefParams } from './types'; // Should match the pattern in react-router @@ -50,59 +51,6 @@ export interface InternalSubRouteRef< getDescription(): string; } -/** @internal */ -export function toInternalSubRouteRef< - TParams extends AnyRouteRefParams = AnyRouteRefParams, ->(resource: SubRouteRef): InternalSubRouteRef { - const r = resource as InternalSubRouteRef; - if (r.$$type !== '@backstage/SubRouteRef') { - throw new Error(`Invalid SubRouteRef, bad type '${r.$$type}'`); - } - - return r; -} - -/** @internal */ -export function isSubRouteRef(opaque: { - $$type: string; -}): opaque is SubRouteRef { - return opaque.$$type === '@backstage/SubRouteRef'; -} - -/** @internal */ -export class SubRouteRefImpl - implements SubRouteRef -{ - readonly $$type = '@backstage/SubRouteRef'; - readonly version = 'v1'; - declare readonly T: never; - - #params: string[]; - #parent: RouteRef; - - constructor(readonly path: string, params: string[], parent: RouteRef) { - this.#params = params; - this.#parent = parent; - } - - getParams(): string[] { - return this.#params; - } - - getParent(): RouteRef { - return this.#parent; - } - - getDescription(): string { - const parent = toInternalRouteRef(this.#parent); - return `at ${this.path} with parent ${parent.getDescription()}`; - } - - toString(): string { - return `SubRouteRef{${this.getDescription()}}`; - } -} - /** * Used in {@link PathParams} type declaration. * @ignore @@ -168,8 +116,9 @@ export function createSubRouteRef< const { path, parent } = config; type Params = PathParams; - const internalParent = toInternalRouteRef(parent); + const internalParent = OpaqueRouteRef.toInternal(parent); const parentParams = internalParent.getParams(); + const parentDescription = internalParent.getDescription(); // Collect runtime parameters from the path, e.g. ['bar', 'baz'] from '/foo/:bar/:baz' const pathParams = path @@ -195,14 +144,22 @@ export function createSubRouteRef< } } - // We ensure that the type of the return type is sane here - const subRouteRef = new SubRouteRefImpl( + return OpaqueSubRouteRef.createInstance('v1', { + T: undefined as unknown as TrimEmptyParams< + MergeParams + >, path, - params as string[], - parent, - ) as SubRouteRef>>; - - // But skip type checking of the return value itself, because the conditional - // type checking of the parent parameter overlap is tricky to express. - return subRouteRef as any; + getParams() { + return params; + }, + getParent() { + return parent; + }, + getDescription() { + return `at ${path} with parent ${parentDescription}`; + }, + toString() { + return `subRouteRef{path='${path}',parent=${parent}}`; + }, + }); } diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.test.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationMessages.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationMessages.ts b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts similarity index 95% rename from packages/core-plugin-api/src/translation/TranslationMessages.ts rename to packages/frontend-plugin-api/src/translation/TranslationMessages.ts index 0108fb9f18..83dd2ef557 100644 --- a/packages/core-plugin-api/src/translation/TranslationMessages.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationMessages.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from './TranslationRef'; /** * Represents a collection of messages to be provided for a given translation ref. * - * @alpha + * @public * @remarks * * This collection of messages can either be used directly as an override for the @@ -43,7 +43,7 @@ export interface TranslationMessages< /** * Options for {@link createTranslationMessages}. * - * @alpha + * @public */ export interface TranslationMessagesOptions< TId extends string, @@ -62,7 +62,7 @@ export interface TranslationMessagesOptions< /** * Creates a collection of messages for a given translation ref. * - * @alpha + * @public */ export function createTranslationMessages< TId extends string, diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationRef.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/frontend-plugin-api/src/translation/TranslationRef.ts similarity index 99% rename from packages/core-plugin-api/src/translation/TranslationRef.ts rename to packages/frontend-plugin-api/src/translation/TranslationRef.ts index c2ac7a5ce3..7686e68343 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationRef.ts @@ -19,7 +19,7 @@ import { TranslationResource, } from './TranslationResource'; -/** @alpha */ +/** @public */ export interface TranslationRef< TId extends string = string, TMessages extends { [key in string]: string } = { [key in string]: string }, @@ -83,7 +83,7 @@ export interface InternalTranslationRef< getDefaultResource(): TranslationResource | undefined; } -/** @alpha */ +/** @public */ export interface TranslationRefOptions< TId extends string, TNestedMessages extends AnyNestedMessages, @@ -164,7 +164,7 @@ class TranslationRefImpl< } } -/** @alpha */ +/** @public */ export function createTranslationRef< TId extends string, const TNestedMessages extends AnyNestedMessages, diff --git a/packages/core-plugin-api/src/translation/TranslationResource.test.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.test.ts similarity index 100% rename from packages/core-plugin-api/src/translation/TranslationResource.test.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.test.ts diff --git a/packages/core-plugin-api/src/translation/TranslationResource.ts b/packages/frontend-plugin-api/src/translation/TranslationResource.ts similarity index 94% rename from packages/core-plugin-api/src/translation/TranslationResource.ts rename to packages/frontend-plugin-api/src/translation/TranslationResource.ts index 0184c55b98..c317bda381 100644 --- a/packages/core-plugin-api/src/translation/TranslationResource.ts +++ b/packages/frontend-plugin-api/src/translation/TranslationResource.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { - TranslationMessages, - TranslationRef, -} from '@backstage/core-plugin-api/alpha'; +import { TranslationMessages } from './TranslationMessages'; +import { TranslationRef } from './TranslationRef'; -/** @alpha */ +/** @public */ export interface TranslationResource { $$type: '@backstage/TranslationResource'; id: TId; @@ -55,7 +53,7 @@ export function toInternalTranslationResource( return r; } -/** @alpha */ +/** @public */ export interface TranslationResourceOptions< TId extends string, TMessages extends { [key in string]: string }, @@ -72,7 +70,7 @@ export interface TranslationResourceOptions< translations: TTranslations; } -/** @alpha */ +/** @public */ export function createTranslationResource< TId extends string, TMessages extends { [key in string]: string }, diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-de.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-de.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json b/packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/counting-sv.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/counting-sv.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-de.json rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-de.json diff --git a/packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/fruits-sv.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/fruits-sv.ts diff --git a/packages/core-plugin-api/src/translation/__fixtures__/refs.ts b/packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts similarity index 100% rename from packages/core-plugin-api/src/translation/__fixtures__/refs.ts rename to packages/frontend-plugin-api/src/translation/__fixtures__/refs.ts diff --git a/packages/frontend-plugin-api/src/translation/index.ts b/packages/frontend-plugin-api/src/translation/index.ts index ef3ccccbe3..121ec62c72 100644 --- a/packages/frontend-plugin-api/src/translation/index.ts +++ b/packages/frontend-plugin-api/src/translation/index.ts @@ -17,12 +17,16 @@ export { type TranslationMessages, type TranslationMessagesOptions, + createTranslationMessages, +} from './TranslationMessages'; +export { type TranslationResource, type TranslationResourceOptions, + createTranslationResource, +} from './TranslationResource'; +export { type TranslationRef, type TranslationRefOptions, - createTranslationMessages, - createTranslationResource, createTranslationRef, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; +} from './TranslationRef'; +export { useTranslationRef } from './useTranslationRef'; diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.test.tsx rename to packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx index b1fbd8a904..a7f5bd41ed 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.test.tsx +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.test.tsx @@ -27,11 +27,11 @@ import { useTranslationRef } from './useTranslationRef'; import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppLanguageSelector } from '../../..//core-app-api/src/apis/implementations/AppLanguageApi'; +import { createTranslationResource } from './TranslationResource'; import { - createTranslationResource, TranslationApi, translationApiRef, -} from '../alpha'; +} from '../apis/definitions/TranslationApi'; import { ErrorApi, errorApiRef } from '../apis'; const plainRef = createTranslationRef({ diff --git a/packages/core-plugin-api/src/translation/useTranslationRef.ts b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts similarity index 98% rename from packages/core-plugin-api/src/translation/useTranslationRef.ts rename to packages/frontend-plugin-api/src/translation/useTranslationRef.ts index b874ac93d8..2e029f82fa 100644 --- a/packages/core-plugin-api/src/translation/useTranslationRef.ts +++ b/packages/frontend-plugin-api/src/translation/useTranslationRef.ts @@ -20,13 +20,13 @@ import { translationApiRef, TranslationFunction, TranslationSnapshot, -} from '../apis/alpha'; +} from '../apis/definitions/TranslationApi'; import { TranslationRef } from './TranslationRef'; // Make sure we don't fill the logs with loading errors for the same ref const loggedRefs = new WeakSet>(); -/** @alpha */ +/** @public */ export const useTranslationRef = < TMessages extends { [key in string]: string }, >( diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 59a81f1b59..989dcf4468 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -216,6 +216,167 @@ describe('createExtension', () => { ); }); + it('should create an extension with relative attachment points', () => { + const extension = createExtension({ + attachTo: [ + { relative: {}, input: 'tabs' }, + { relative: { kind: 'page' }, input: 'tabs' }, + { relative: { name: 'index' }, input: 'tabs' }, + { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, + ], + output: [stringDataRef], + factory: () => [stringDataRef('bar')], + }); + expect(String(extension)).toBe( + 'ExtensionDefinition{attachTo=@tabs+page:@tabs+/index@tabs+page:/index@tabs}', + ); + }); + + it('should create an extension with relative attachment points by reference', () => { + const baseOpts = { + attachTo: { id: 'root', input: 'children' }, + inputs: { + tabs: createExtensionInput([stringDataRef]), + }, + output: [], + factory: () => [], + }; + const parent1 = createExtension({ + ...baseOpts, + }); + const parent2 = createExtension({ + ...baseOpts, + kind: 'page', + }); + const parent3 = createExtension({ + ...baseOpts, + name: 'index', + }); + const parent4 = createExtension({ + ...baseOpts, + inputs: {}, + kind: 'page', + name: 'index', + }).override({ + inputs: { + otherTabs: createExtensionInput([stringDataRef]), + }, + factory: () => [], + }); + const extension = createExtension({ + attachTo: [ + parent1.inputs.tabs, + parent2.inputs.tabs, + parent3.inputs.tabs, + parent4.inputs.otherTabs, + ], + output: [stringDataRef], + factory: () => [stringDataRef('bar')], + }); + expect(String(extension)).toBe( + 'ExtensionDefinition{attachTo=@tabs+page:@tabs+/index@tabs+page:/index@otherTabs}', + ); + const overrdeExtension = extension.override({ + attachTo: [ + parent2.inputs.tabs, + parent3.inputs.tabs, + parent4.inputs.otherTabs, + ], + }); + expect(String(overrdeExtension)).toBe( + 'ExtensionDefinition{attachTo=page:@tabs+/index@tabs+page:/index@otherTabs}', + ); + }); + + it('should provide type safe attachments by reference', () => { + const parent = createExtension({ + attachTo: { id: 'root', input: 'children' }, + inputs: { + string: createExtensionInput([stringDataRef]), + stringOpt: createExtensionInput([stringDataRef.optional()]), + number: createExtensionInput([numberDataRef]), + numberOpt: createExtensionInput([numberDataRef.optional()]), + both: createExtensionInput([stringDataRef, numberDataRef]), + bothOptString: createExtensionInput([ + stringDataRef.optional(), + numberDataRef, + ]), + bothOptNumber: createExtensionInput([ + stringDataRef, + numberDataRef.optional(), + ]), + bothOpt: createExtensionInput([ + stringDataRef.optional(), + numberDataRef.optional(), + ]), + }, + output: [], + factory: () => [], + }); + const strOutExt = createExtension({ + attachTo: parent.inputs.string, + output: [stringDataRef], + factory: () => [stringDataRef('str')], + }); + strOutExt.override({ + attachTo: parent.inputs.string, + }); + strOutExt.override({ + attachTo: parent.inputs.stringOpt, + }); + strOutExt.override({ + // @ts-expect-error + attachTo: parent.inputs.number, + }); + strOutExt.override({ + attachTo: parent.inputs.numberOpt, + }); + strOutExt.override({ + // @ts-expect-error + attachTo: parent.inputs.both, + }); + strOutExt.override({ + attachTo: parent.inputs.bothOptNumber, + }); + strOutExt.override({ + // @ts-expect-error + attachTo: parent.inputs.bothOptString, + }); + strOutExt.override({ + attachTo: parent.inputs.bothOpt, + }); + const numberOutExt = createExtension({ + // @ts-expect-error + attachTo: parent.inputs.string, + output: [numberDataRef], + factory: () => [numberDataRef(1)], + }); + numberOutExt.override({ + // @ts-expect-error + attachTo: parent.inputs.string, + }); + numberOutExt.override({ + attachTo: parent.inputs.number, + }); + const bothOutExt = createExtension({ + attachTo: parent.inputs.both, + output: [numberDataRef, stringDataRef], + factory: () => [numberDataRef(1), stringDataRef('str')], + }); + // TODO(Rugvip): Potentially encapsulate the parent input type in the extension, until then we can't verify this + bothOutExt.override({ + output: [numberDataRef.optional(), stringDataRef], + factory: () => [stringDataRef('str')], + }); + bothOutExt.override({ + // @ts-expect-error + attachTo: parent.inputs.both, + output: [numberDataRef.optional(), stringDataRef], + factory: () => [stringDataRef('str')], + }); + expect('types').not.toBe('broken'); + }); + it('should create an extension with input', () => { const extension = createExtension({ attachTo: { id: 'root', input: 'default' }, @@ -1157,4 +1318,26 @@ describe('createExtension', () => { ); }); }); + + it('should old inputs without context support', () => { + const legacyInput = createExtensionInput([numberDataRef]); + + // old API without context + delete (legacyInput as any).context; + delete (legacyInput as any).withContext; + + const extension = createExtension({ + attachTo: { id: 'root', input: 'default' }, + output: [stringDataRef], + inputs: { + foo: legacyInput, + }, + factory({ inputs }) { + unused(inputs.foo); + return [stringDataRef('output')]; + }, + }); + + expect(extension.inputs.foo).toBe(legacyInput); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 0c0b75a572..9b55505056 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -20,7 +20,10 @@ import { ResolvedInputValueOverrides, resolveInputOverrides, } from './resolveInputOverrides'; -import { createExtensionDataContainer } from '@internal/frontend'; +import { + createExtensionDataContainer, + OpaqueExtensionInput, +} from '@internal/frontend'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; @@ -44,13 +47,12 @@ export const ctxParamsSymbol = Symbol('params'); * Convert a single extension input into a matching resolved input. * @public */ -export type ResolvedExtensionInput< - TExtensionInput extends ExtensionInput, -> = TExtensionInput['extensionData'] extends Array - ? { - node: AppNode; - } & ExtensionDataContainer - : never; +export type ResolvedExtensionInput = + TExtensionInput['extensionData'] extends Array + ? { + node: AppNode; + } & ExtensionDataContainer + : never; /** * Converts an extension input map into a matching collection of resolved inputs. @@ -58,7 +60,7 @@ export type ResolvedExtensionInput< */ export type ResolvedExtensionInputs< TInputs extends { - [name in string]: ExtensionInput; + [name in string]: ExtensionInput; }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] @@ -91,50 +93,104 @@ type JoinStringUnion< : JoinStringUnion : TResult; +/** @ignore */ +export type RequiredExtensionIds = + UExtensionData extends any + ? UExtensionData['config']['optional'] extends true + ? never + : UExtensionData['id'] + : never; + /** @ignore */ export type VerifyExtensionFactoryOutput< UDeclaredOutput extends ExtensionDataRef, UFactoryOutput extends ExtensionDataValue, -> = ( - UDeclaredOutput extends any - ? UDeclaredOutput['config']['optional'] extends true - ? never - : UDeclaredOutput['id'] - : never -) extends infer IRequiredOutputIds - ? [IRequiredOutputIds] extends [UFactoryOutput['id']] - ? [UFactoryOutput['id']] extends [UDeclaredOutput['id']] - ? {} - : `Error: The extension factory has undeclared output(s): ${JoinStringUnion< - Exclude - >}` - : `Error: The extension factory is missing the following output(s): ${JoinStringUnion< - Exclude +> = [RequiredExtensionIds] extends [UFactoryOutput['id']] + ? [UFactoryOutput['id']] extends [UDeclaredOutput['id']] + ? {} + : `Error: The extension factory has undeclared output(s): ${JoinStringUnion< + Exclude >}` - : never; + : `Error: The extension factory is missing the following output(s): ${JoinStringUnion< + Exclude, UFactoryOutput['id']> + >}`; -/** @public */ -export type ExtensionAttachToSpec = - | { id: string; input: string } - | Array<{ id: string; input: string }>; +/** @ignore */ +export type VerifyExtensionAttachTo< + UOutput extends ExtensionDataRef, + UParentInput extends ExtensionDataRef, +> = ExtensionDataRef extends UParentInput + ? {} + : [RequiredExtensionIds] extends [RequiredExtensionIds] + ? {} + : `Error: This parent extension input requires the following extension data, but it is not declared as guaranteed output of this extension: ${JoinStringUnion< + Exclude, RequiredExtensionIds> + >}`; + +/** + * Specifies where an extension should attach in the extension tree. + * + * @remarks + * + * A standard attachment point declaration will specify the ID of the parent extension, as well as the name of the input to attach to. + * + * There are three more advanced forms that are available for more complex use-cases: + * + * 1. Relative attachment points: using the `relative` property instead of `id`, the attachment point is resolved relative to the current plugin. + * 2. Extension input references: using a reference in code to another extension's input in the same plugin. These references are always relative. + * 3. Array of attachment points: an array of attachment points can be used to clone and attach to multiple extensions at once. + * + * @example + * ```ts + * // Attach to a specific extension by full ID + * { id: 'app/routes', input: 'routes' } + * + * // Attach to an extension in the same plugin by kind + * { relative: { kind: 'page' }, input: 'actions' } + * + * // Attach to a specific input of another extension + * const page = ParentBlueprint.make({ ... }); + * const child = ChildBlueprint.make({ attachTo: page.inputs.children }); + * + * // Attach to multiple parents at once + * [ + * { id: 'page/home', input: 'widgets' }, + * { relative: { kind: 'page' }, input: 'widgets' }, + * ] + * ``` + * + * @public + */ +export type ExtensionDefinitionAttachTo< + UParentInputs extends ExtensionDataRef = ExtensionDataRef, +> = + | { id: string; input: string; relative?: never } + | { relative: { kind?: string; name?: string }; input: string; id?: never } + | ExtensionInput + | Array< + | { id: string; input: string; relative?: never } + | { + relative: { kind?: string; name?: string }; + input: string; + id?: never; + } + | ExtensionInput + >; /** @public */ export type CreateExtensionOptions< TKind extends string | undefined, TName extends string | undefined, UOutput extends ExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + TInputs extends { [inputName in string]: ExtensionInput }, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, + UParentInputs extends ExtensionDataRef, > = { kind?: TKind; name?: TName; - attachTo: ExtensionAttachToSpec; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; disabled?: boolean; inputs?: TInputs; output: Array; @@ -158,12 +214,7 @@ export type ExtensionDefinitionParameters = { configInput?: { [K in string]: any }; config?: { [K in string]: any }; output?: ExtensionDataRef; - inputs?: { - [KName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; + inputs?: { [KName in string]: ExtensionInput }; params?: object | ExtensionBlueprintDefineParams; }; @@ -182,11 +233,25 @@ type AnyParamsInput = ) => ReturnType>); /** @public */ -export type ExtensionDefinition< - T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, -> = { +export interface ExtensionDefinition< + TParams extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> { $$type: '@backstage/ExtensionDefinition'; - readonly T: T; + readonly T: TParams; +} + +/** @public */ +export interface OverridableExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> extends ExtensionDefinition { + /** + * References to the inputs of this extension, which can be used to attach child extensions. + */ + readonly inputs: { + [K in keyof T['inputs']]: ExtensionInput< + T['inputs'][K] extends ExtensionInput ? IData : never + >; + }; override< TExtensionConfigSchema extends { @@ -194,17 +259,19 @@ export type ExtensionDefinition< }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, - TExtraInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + TExtraInputs extends { [inputName in string]: ExtensionInput }, TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, >( args: Expand< { - attachTo?: ExtensionAttachToSpec; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; disabled?: boolean; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & @@ -265,7 +332,7 @@ export type ExtensionDefinition< : UNewOutput, UFactoryOutput >, - ): ExtensionDefinition<{ + ): OverridableExtensionDefinition<{ kind: T['kind']; name: T['name']; output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; @@ -284,7 +351,31 @@ export type ExtensionDefinition< }> >; }>; -}; +} + +/** + * @internal + */ +function bindInputs( + inputs: { [inputName in string]: ExtensionInput } | undefined, + kind?: string, + name?: string, +) { + if (!inputs) { + return {}; + } + + return Object.fromEntries( + Object.entries(inputs).map(([inputName, input]) => [ + inputName, + OpaqueExtensionInput.toInternal(input).withContext?.({ + kind, + name, + input: inputName, + }) ?? input, + ]), + ); +} /** * Creates a new extension definition for installation in a Backstage app. @@ -323,16 +414,12 @@ export type ExtensionDefinition< */ export function createExtension< UOutput extends ExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + TInputs extends { [inputName in string]: ExtensionInput }, TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, const TKind extends string | undefined = undefined, const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, >( options: CreateExtensionOptions< TKind, @@ -340,9 +427,10 @@ export function createExtension< UOutput, TInputs, TConfigSchema, - UFactoryOutput + UFactoryOutput, + UParentInputs >, -): ExtensionDefinition<{ +): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} : { @@ -404,7 +492,7 @@ export function createExtension< name: options.name, attachTo: options.attachTo, disabled: options.disabled ?? false, - inputs: options.inputs ?? {}, + inputs: bindInputs(options.inputs, options.kind, options.name), output: options.output, configSchema, factory: options.factory, @@ -416,12 +504,41 @@ export function createExtension< if (options.name) { parts.push(`name=${options.name}`); } - parts.push( - `attachTo=${[options.attachTo] - .flat() - .map(a => `${a.id}@${a.input}`) - .join('+')}`, - ); + const attachTo = [options.attachTo] + .flat() + .map(aAny => { + const a = aAny as ExtensionDefinitionAttachTo; + if (OpaqueExtensionInput.isType(a)) { + const { context } = OpaqueExtensionInput.toInternal(a); + if (!context) { + return ''; + } + let id = ''; + if (context?.kind) { + id = `${context?.kind}:${id}`; + } + if (context?.name) { + id = `${id}/${context?.name}`; + } + return `${id}@${context.input}`; + } + if ('relative' in a && a.relative) { + let id = ''; + if (a.relative.kind) { + id = `${a.relative.kind}:${id}`; + } + if (a.relative.name) { + id = `${id}/${a.relative.name}`; + } + return `${id}@${a.input}`; + } + if ('id' in a) { + return `${a.id}@${a.input}`; + } + throw new Error('Invalid attachment point specification'); + }) + .join('+'); + parts.push(`attachTo=${attachTo}`); return `ExtensionDefinition{${parts.join(',')}}`; }, override(overrideOptions) { @@ -449,9 +566,17 @@ export function createExtension< return createExtension({ kind: options.kind, name: options.name, - attachTo: overrideOptions.attachTo ?? options.attachTo, + attachTo: (overrideOptions.attachTo ?? + options.attachTo) as ExtensionDefinitionAttachTo, disabled: overrideOptions.disabled ?? options.disabled, - inputs: { ...overrideOptions.inputs, ...options.inputs }, + inputs: bindInputs( + { + ...(options.inputs ?? {}), + ...(overrideOptions.inputs ?? {}), + }, + options.kind, + options.name, + ), output: (overrideOptions.output ?? options.output) as ExtensionDataRef[], config: @@ -518,7 +643,7 @@ export function createExtension< return deduplicatedResult.values(); }, - }) as ExtensionDefinition; + }) as OverridableExtensionDefinition; }, }); } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index e4cc876ff0..4759441187 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -30,7 +30,7 @@ import { } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; -import { ExtensionDefinition } from './createExtension'; +import { createExtension, ExtensionDefinition } from './createExtension'; import { createExtensionDataContainer, OpaqueExtensionDefinition, @@ -1345,6 +1345,235 @@ describe('createExtensionBlueprint', () => { ).toThrow('Refused to override params and factory at the same time'); }); + describe('with relative attachment points', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + + it('should create an extension with relative attachment points', () => { + const blueprint = createExtensionBlueprint({ + kind: 'test', + attachTo: [ + { relative: {}, input: 'tabs' }, + { relative: { kind: 'page' }, input: 'tabs' }, + { relative: { name: 'index' }, input: 'tabs' }, + { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, + ], + output: [dataRef], + factory: () => [dataRef('bar')], + }); + + expect(String(blueprint.make({ params: {} }))).toBe( + 'ExtensionDefinition{kind=test,attachTo=@tabs+page:@tabs+/index@tabs+page:/index@tabs}', + ); + expect( + String( + blueprint.make({ + attachTo: [ + { relative: { kind: 'page' }, input: 'tabs' }, + { relative: { name: 'index' }, input: 'tabs' }, + { relative: { kind: 'page', name: 'index' }, input: 'tabs' }, + ], + params: {}, + }), + ), + ).toBe( + 'ExtensionDefinition{kind=test,attachTo=page:@tabs+/index@tabs+page:/index@tabs}', + ); + expect( + String( + blueprint.makeWithOverrides({ + attachTo: { + relative: { kind: 'page', name: 'index' }, + input: 'tabs', + }, + factory: orig => orig({}), + }), + ), + ).toBe( + 'ExtensionDefinition{kind=test,attachTo=page:/index@tabs}', + ); + }); + + it('should create an extension with relative attachment points by reference', () => { + const baseOpts = { + attachTo: { id: 'root', input: 'children' }, + inputs: { + tabs: createExtensionInput([dataRef]), + }, + output: [], + factory: () => [], + }; + const parent1 = createExtension({ + ...baseOpts, + }); + const parent2 = createExtension({ + ...baseOpts, + kind: 'page', + }); + const parent3 = createExtension({ + ...baseOpts, + name: 'index', + }); + const parent4 = createExtension({ + ...baseOpts, + inputs: {}, + kind: 'page', + name: 'index', + }).override({ + inputs: { + otherTabs: createExtensionInput([dataRef]), + }, + factory: () => [], + }); + const blueprint = createExtensionBlueprint({ + kind: 'test', + attachTo: [ + parent1.inputs.tabs, + parent2.inputs.tabs, + parent3.inputs.tabs, + parent4.inputs.otherTabs, + ], + output: [dataRef], + factory: () => [dataRef('bar')], + }); + expect(String(blueprint.make({ params: {} }))).toBe( + 'ExtensionDefinition{kind=test,attachTo=@tabs+page:@tabs+/index@tabs+page:/index@otherTabs}', + ); + expect( + String( + blueprint.make({ + attachTo: [ + parent2.inputs.tabs, + parent3.inputs.tabs, + parent4.inputs.otherTabs, + ], + params: {}, + }), + ), + ).toBe( + 'ExtensionDefinition{kind=test,attachTo=page:@tabs+/index@tabs+page:/index@otherTabs}', + ); + expect( + String( + blueprint.makeWithOverrides({ + attachTo: parent4.inputs.otherTabs, + factory: orig => orig({}), + }), + ), + ).toBe( + 'ExtensionDefinition{kind=test,attachTo=page:/index@otherTabs}', + ); + }); + + it('should provide type safe attachments by reference', () => { + const stringDataRef = createExtensionDataRef().with({ + id: 'test.string', + }); + const numberDataRef = createExtensionDataRef().with({ + id: 'test.number', + }); + + const parent = createExtensionBlueprint({ + kind: 'test-parent', + attachTo: { id: 'root', input: 'children' }, + inputs: { + string: createExtensionInput([stringDataRef]), + stringOpt: createExtensionInput([stringDataRef.optional()]), + number: createExtensionInput([numberDataRef]), + numberOpt: createExtensionInput([numberDataRef.optional()]), + both: createExtensionInput([stringDataRef, numberDataRef]), + bothOptString: createExtensionInput([ + stringDataRef.optional(), + numberDataRef, + ]), + bothOptNumber: createExtensionInput([ + stringDataRef, + numberDataRef.optional(), + ]), + bothOpt: createExtensionInput([ + stringDataRef.optional(), + numberDataRef.optional(), + ]), + }, + output: [], + factory: () => [], + }).make({ params: {} }); + const strOutExt = createExtensionBlueprint({ + kind: 'test', + attachTo: parent.inputs.string, + output: [stringDataRef], + factory: () => [stringDataRef('str')], + }); + strOutExt.make({ + attachTo: parent.inputs.string, + params: {}, + }); + strOutExt.makeWithOverrides({ + attachTo: parent.inputs.stringOpt, + factory: orig => orig({}), + }); + strOutExt.make({ + // @ts-expect-error + attachTo: parent.inputs.number, + params: {}, + }); + strOutExt.makeWithOverrides({ + attachTo: parent.inputs.numberOpt, + factory: orig => orig({}), + }); + strOutExt.make({ + // @ts-expect-error + attachTo: parent.inputs.both, + params: {}, + }); + strOutExt.make({ + attachTo: parent.inputs.bothOptNumber, + params: {}, + }); + strOutExt.make({ + // @ts-expect-error + attachTo: parent.inputs.bothOptString, + params: {}, + }); + strOutExt.make({ + attachTo: parent.inputs.bothOpt, + params: {}, + }); + const numberOutExt = createExtensionBlueprint({ + kind: 'test', + // @ts-expect-error + attachTo: parent.inputs.string, + output: [numberDataRef], + factory: () => [numberDataRef(1)], + }); + numberOutExt.make({ + // @ts-expect-error + attachTo: parent.inputs.string, + params: {}, + }); + numberOutExt.makeWithOverrides({ + attachTo: parent.inputs.number, + factory: orig => orig({}), + }); + const bothOutExt = createExtensionBlueprint({ + kind: 'test', + attachTo: parent.inputs.both, + output: [numberDataRef, stringDataRef], + factory: () => [numberDataRef(1), stringDataRef('str')], + }); + bothOutExt.makeWithOverrides({ + output: [numberDataRef.optional(), stringDataRef], + factory: orig => orig({}), + }); + bothOutExt.makeWithOverrides({ + // @ts-expect-error + attachTo: parent.inputs.both, + output: [numberDataRef.optional(), stringDataRef], + factory: orig => orig({}), + }); + expect('types').not.toBe('broken'); + }); + }); + describe('with advanced parameter types', () => { const testDataRef = createExtensionDataRef().with({ id: 'test' }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 1a24eed712..c6723388ae 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -18,12 +18,13 @@ import { ApiHolder, AppNode } from '../apis'; import { Expand } from '@backstage/types'; import { OpaqueType } from '@internal/opaque'; import { - ExtensionAttachToSpec, - ExtensionDefinition, + ExtensionDefinitionAttachTo, + OverridableExtensionDefinition, ResolvedExtensionInputs, VerifyExtensionFactoryOutput, createExtension, ctxParamsSymbol, + VerifyExtensionAttachTo, } from './createExtension'; import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; @@ -103,18 +104,15 @@ export type CreateExtensionBlueprintOptions< TKind extends string, TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + TInputs extends { [inputName in string]: ExtensionInput }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, TDataRefs extends { [name in string]: ExtensionDataRef }, + UParentInputs extends ExtensionDataRef, > = { kind: TKind; - attachTo: ExtensionAttachToSpec; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; disabled?: boolean; inputs?: TInputs; output: Array; @@ -186,12 +184,7 @@ export type ExtensionBlueprintParameters = { configInput?: { [K in string]: any }; config?: { [K in string]: any }; output?: ExtensionDataRef; - inputs?: { - [KName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; + inputs?: { [KName in string]: ExtensionInput }; dataRefs?: { [name in string]: ExtensionDataRef }; }; @@ -222,16 +215,18 @@ export interface ExtensionBlueprint< make< TName extends string | undefined, TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, >(args: { name?: TName; - attachTo?: ExtensionAttachToSpec; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo, UParentInputs>; disabled?: boolean; params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput : T['params'] extends ExtensionBlueprintDefineParams ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: defineParams => defineParams() })`' : T['params']; - }): ExtensionDefinition<{ + }): OverridableExtensionDefinition<{ kind: T['kind']; name: string | undefined extends TName ? undefined : TName; config: T['config']; @@ -254,15 +249,17 @@ export interface ExtensionBlueprint< }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, - TExtraInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput } = {}, >(args: { name?: TName; - attachTo?: ExtensionAttachToSpec; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; disabled?: boolean; inputs?: TExtraInputs & { [KName in keyof T['inputs']]?: `Error: Input '${KName & @@ -306,27 +303,31 @@ export interface ExtensionBlueprint< : UNewOutput, UFactoryOutput >; - }): ExtensionDefinition<{ - config: (string extends keyof TExtensionConfigSchema - ? {} - : { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }) & - T['config']; - configInput: (string extends keyof TExtensionConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TExtensionConfigSchema]: ReturnType< - TExtensionConfigSchema[key] + }): OverridableExtensionDefinition<{ + config: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType >; - }> - >) & - T['configInput']; + }) & + T['config'] + >; + configInput: Expand< + (string extends keyof TExtensionConfigSchema + ? {} + : z.input< + z.ZodObject<{ + [key in keyof TExtensionConfigSchema]: ReturnType< + TExtensionConfigSchema[key] + >; + }> + >) & + T['configInput'] + >; output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; - inputs: T['inputs'] & TExtraInputs; + inputs: Expand; kind: T['kind']; name: string | undefined extends TName ? undefined : TName; params: T['params']; @@ -455,15 +456,11 @@ function unwrapParams( export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, - TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + TInputs extends { [inputName in string]: ExtensionInput }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, TKind extends string, + UParentInputs extends ExtensionDataRef, TDataRefs extends { [name in string]: ExtensionDataRef } = never, >( options: CreateExtensionBlueprintOptions< @@ -473,7 +470,8 @@ export function createExtensionBlueprint< TInputs, TConfigSchema, UFactoryOutput, - TDataRefs + TDataRefs, + UParentInputs >, ): ExtensionBlueprint<{ kind: TKind; @@ -509,7 +507,8 @@ export function createExtensionBlueprint< return createExtension({ kind: options.kind, name: args.name, - attachTo: args.attachTo ?? options.attachTo, + attachTo: (args.attachTo ?? + options.attachTo) as ExtensionDefinitionAttachTo, disabled: args.disabled ?? options.disabled, inputs: options.inputs, output: options.output as ExtensionDataRef[], @@ -519,13 +518,14 @@ export function createExtensionBlueprint< unwrapParams(args.params, ctx, defineParams, options.kind), ctx, ) as Iterable>, - }) as ExtensionDefinition; + }) as OverridableExtensionDefinition; }, makeWithOverrides(args) { return createExtension({ kind: options.kind, name: args.name, - attachTo: args.attachTo ?? options.attachTo, + attachTo: (args.attachTo ?? + options.attachTo) as ExtensionDefinitionAttachTo, disabled: args.disabled ?? options.disabled, inputs: { ...args.inputs, ...options.inputs }, output: (args.output ?? options.output) as ExtensionDataRef[], @@ -576,7 +576,7 @@ export function createExtensionBlueprint< }, ) as Iterable>; }, - }) as ExtensionDefinition; + }) as OverridableExtensionDefinition; }, } as ExtensionBlueprint<{ kind: TKind; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts index 084758b793..279878542a 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { OpaqueExtensionInput } from '@internal/frontend'; import { createExtensionDataRef } from './createExtensionDataRef'; import { ExtensionInput, createExtensionInput } from './createExtensionInput'; @@ -29,6 +30,7 @@ describe('createExtensionInput', () => { $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], config: { singleton: false, optional: false }, + withContext: expect.any(Function), }); const x1: ExtensionInput< @@ -54,6 +56,20 @@ describe('createExtensionInput', () => { unused(x1, x2, x3, x4); }); + it('should attach a context to the input', () => { + const input = createExtensionInput([stringDataRef, numberDataRef]); + const context = { input: 'test1', kind: 'test2', name: 'test3' }; + const inputWithContext = + OpaqueExtensionInput.toInternal(input).withContext?.(context); + expect(inputWithContext).toEqual({ + $$type: '@backstage/ExtensionInput', + extensionData: [stringDataRef, numberDataRef], + config: { singleton: false, optional: false }, + withContext: expect.any(Function), + context, + }); + }); + it('should create a singleton input', () => { const input = createExtensionInput([stringDataRef, numberDataRef], { singleton: true, @@ -62,6 +78,7 @@ describe('createExtensionInput', () => { $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], config: { singleton: true, optional: false }, + withContext: expect.any(Function), }); const x1: ExtensionInput< @@ -96,6 +113,7 @@ describe('createExtensionInput', () => { $$type: '@backstage/ExtensionInput', extensionData: [stringDataRef, numberDataRef], config: { singleton: true, optional: true }, + withContext: expect.any(Function), }); const x1: ExtensionInput< diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts index 7209e6f647..57040c8adf 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionInput.ts @@ -14,17 +14,28 @@ * limitations under the License. */ +import { + ExtensionInputContext, + OpaqueExtensionInput, +} from '@internal/frontend'; import { ExtensionDataRef } from './createExtensionDataRef'; /** @public */ export interface ExtensionInput< - UExtensionData extends ExtensionDataRef, - TConfig extends { singleton: boolean; optional: boolean }, + UExtensionData extends ExtensionDataRef< + unknown, + string, + { optional?: true } + > = ExtensionDataRef, + TConfig extends { singleton: boolean; optional: boolean } = { + singleton: boolean; + optional: boolean; + }, > { - $$type: '@backstage/ExtensionInput'; - extensionData: Array; - config: TConfig; - replaces?: Array<{ id: string; input: string }>; + readonly $$type: '@backstage/ExtensionInput'; + readonly extensionData: Array; + readonly config: TConfig; + readonly replaces?: Array<{ id: string; input: string }>; } /** @public */ @@ -61,8 +72,7 @@ export function createExtensionInput< } } } - return { - $$type: '@backstage/ExtensionInput', + const baseOptions = { extensionData, config: { singleton: Boolean(config?.singleton) as TConfig['singleton'] extends true @@ -73,11 +83,21 @@ export function createExtensionInput< : false, }, replaces: config?.replaces, - } as ExtensionInput< + }; + + function createInstance(parent?: ExtensionInputContext): ExtensionInput< UExtensionData, { singleton: TConfig['singleton'] extends true ? true : false; optional: TConfig['optional'] extends true ? true : false; } - >; + > { + return OpaqueExtensionInput.createInstance(undefined, { + ...baseOptions, + context: parent, + withContext: createInstance, + }); + } + + return createInstance(); } diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 5555a0b47c..68b5826baa 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -18,7 +18,10 @@ import { OpaqueExtensionDefinition, OpaqueFrontendPlugin, } from '@internal/frontend'; -import { ExtensionDefinition } from './createExtension'; +import { + ExtensionDefinition, + OverridableExtensionDefinition, +} from './createExtension'; import { Extension, resolveExtensionDefinition, @@ -104,7 +107,9 @@ export interface OverridableFrontendPlugin< [id in string]: ExtensionDefinition; }, > extends FrontendPlugin { - getExtension(id: TId): TExtensionMap[TId]; + getExtension( + id: TId, + ): OverridableExtensionDefinition; withOverrides(options: { extensions: Array; diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 45c80d4a90..6ce1bb4998 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -15,12 +15,13 @@ */ export { coreExtensionData } from './coreExtensionData'; +export { createExtension } from './createExtension'; export { - createExtension, type ExtensionDefinition, + type ExtensionDefinitionAttachTo, type ExtensionDefinitionParameters, - type ExtensionAttachToSpec, type CreateExtensionOptions, + type OverridableExtensionDefinition, type ResolvedExtensionInput, type ResolvedExtensionInputs, } from './createExtension'; @@ -54,7 +55,11 @@ export { type FrontendFeatureLoader, type CreateFrontendFeatureLoaderOptions, } from './createFrontendFeatureLoader'; -export { type Extension } from './resolveExtensionDefinition'; +export { + type Extension, + type ExtensionAttachTo, + type ExtensionAttachToSpec, +} from './resolveExtensionDefinition'; export { type ExtensionDataContainer, type FeatureFlagConfig, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index a74d908024..79749af77c 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -14,11 +14,21 @@ * limitations under the License. */ +import { + createExtensionDataRef, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from './createExtension'; import { ResolveExtensionId, resolveExtensionDefinition, } from './resolveExtensionDefinition'; +import { + OpaqueExtensionDefinition, + OpaqueExtensionInput, +} from '@internal/frontend'; + +const testDataRef = createExtensionDataRef().with({ id: 'test' }); describe('resolveExtensionDefinition', () => { const baseDef = { @@ -26,6 +36,7 @@ describe('resolveExtensionDefinition', () => { T: undefined as any, version: 'v2', attachTo: { id: '', input: '' }, + inputs: {}, disabled: false, override: () => ({} as ExtensionDefinition), }; @@ -60,6 +71,111 @@ describe('resolveExtensionDefinition', () => { 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined', ); }); + + it('should resolve extension input references', () => { + const baseInpuf = OpaqueExtensionInput.toInternal( + createExtensionInput([testDataRef]), + ); + expect( + resolveExtensionDefinition( + OpaqueExtensionDefinition.toInternal({ + ...baseDef, + attachTo: baseInpuf.withContext?.({ + kind: 'parent', + name: 'example', + input: 'children', + }), + }), + { namespace: 'test' }, + ).attachTo, + ).toEqual({ + id: 'parent:test/example', + input: 'children', + }); + + expect( + resolveExtensionDefinition( + OpaqueExtensionDefinition.toInternal({ + ...baseDef, + attachTo: baseInpuf.withContext?.({ + name: 'example', + input: 'children', + }), + }), + { namespace: 'test' }, + ).attachTo, + ).toEqual({ + id: 'test/example', + input: 'children', + }); + + expect( + resolveExtensionDefinition( + OpaqueExtensionDefinition.toInternal({ + ...baseDef, + attachTo: baseInpuf.withContext?.({ + kind: 'parent', + input: 'children', + }), + }), + { namespace: 'test' }, + ).attachTo, + ).toEqual({ + id: 'parent:test', + input: 'children', + }); + + expect( + resolveExtensionDefinition( + OpaqueExtensionDefinition.toInternal({ + ...baseDef, + attachTo: baseInpuf.withContext?.({ + input: 'children', + }), + }), + { namespace: 'test' }, + ).attachTo, + ).toEqual({ + id: 'test', + input: 'children', + }); + + expect( + resolveExtensionDefinition( + OpaqueExtensionDefinition.toInternal({ + ...baseDef, + attachTo: [ + baseInpuf.withContext?.({ + kind: 'k1', + input: 'children', + }), + baseInpuf.withContext?.({ + kind: 'k2', + input: 'children', + }), + baseInpuf.withContext?.({ + kind: 'k3', + input: 'children', + }), + ], + }), + { namespace: 'test' }, + ).attachTo, + ).toEqual([ + { + id: 'k1:test', + input: 'children', + }, + { + id: 'k2:test', + input: 'children', + }, + { + id: 'k3:test', + input: 'children', + }, + ]); + }); }); describe('old resolveExtensionDefinition', () => { @@ -68,6 +184,7 @@ describe('old resolveExtensionDefinition', () => { T: undefined as any, version: 'v1', attachTo: { id: '', input: '' }, + inputs: {}, disabled: false, override: () => ({} as ExtensionDefinition), }; diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index a5b6859e1a..f99d586e87 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -16,7 +16,7 @@ import { ApiHolder, AppNode } from '../apis'; import { - ExtensionAttachToSpec, + ExtensionDefinitionAttachTo, ExtensionDefinition, ExtensionDefinitionParameters, ResolvedExtensionInputs, @@ -24,7 +24,21 @@ import { import { PortableSchema } from '../schema'; import { ExtensionInput } from './createExtensionInput'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; -import { OpaqueExtensionDefinition } from '@internal/frontend'; +import { + OpaqueExtensionDefinition, + OpaqueExtensionInput, +} from '@internal/frontend'; + +/** @public */ +export type ExtensionAttachTo = + | { id: string; input: string } + | Array<{ id: string; input: string }>; + +/** + * @deprecated Use {@link ExtensionAttachTo} instead. + * @public + */ +export type ExtensionAttachToSpec = ExtensionAttachTo; /** @public */ export interface Extension { @@ -68,22 +82,14 @@ export type InternalExtension = Extension< } | { readonly version: 'v2'; - readonly inputs: { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; + readonly inputs: { [inputName in string]: ExtensionInput }; readonly output: Array; factory(options: { apis: ApiHolder; node: AppNode; config: TConfig; inputs: ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; + [inputName in string]: ExtensionInput; }>; }): Iterable>; } @@ -126,24 +132,11 @@ export type ResolveExtensionId< : never : never; -/** @internal */ -export function resolveExtensionDefinition< - T extends ExtensionDefinitionParameters, ->( - definition: ExtensionDefinition, - context?: { namespace?: string }, -): Extension { - const internalDefinition = OpaqueExtensionDefinition.toInternal(definition); - const { - name, - kind, - namespace: _skip1, - override: _skip2, - ...rest - } = internalDefinition; - - const namespace = internalDefinition.namespace ?? context?.namespace; - +function resolveExtensionId( + kind?: string, + namespace?: string, + name?: string, +): string { const namePart = name && namespace ? `${namespace}/${name}` : namespace || name; if (!namePart) { @@ -152,10 +145,75 @@ export function resolveExtensionDefinition< ); } - const id = kind ? `${kind}:${namePart}` : namePart; + return kind ? `${kind}:${namePart}` : namePart; +} + +function resolveAttachTo( + attachTo: ExtensionDefinitionAttachTo, + namespace?: string, +): ExtensionAttachToSpec { + const resolveSpec = ( + spec: Exclude>, + ): { id: string; input: string } => { + if (OpaqueExtensionInput.isType(spec)) { + const { context } = OpaqueExtensionInput.toInternal(spec); + if (!context) { + throw new Error( + 'Invalid input object without a parent extension used as attachment point', + ); + } + return { + id: resolveExtensionId(context.kind, namespace, context.name), + input: context.input, + }; + } + if ('relative' in spec && spec.relative) { + return { + id: resolveExtensionId( + spec.relative.kind, + namespace, + spec.relative.name, + ), + input: spec.input, + }; + } + if ('id' in spec) { + return { id: spec.id, input: spec.input }; + } + throw new Error('Invalid attachment point specification'); + }; + + if (Array.isArray(attachTo)) { + return attachTo.map(resolveSpec); + } + + return resolveSpec(attachTo); +} + +/** @internal */ +export function resolveExtensionDefinition< + T extends ExtensionDefinitionParameters, +>( + definition: ExtensionDefinition, + context?: { namespace?: string }, +): Extension { + const internalDefinition = OpaqueExtensionDefinition.toInternal(definition); + + const { + name, + kind, + namespace: internalNamespace, + override: _skip2, + attachTo, + ...rest + } = internalDefinition; + + const namespace = internalNamespace ?? context?.namespace; + const id = resolveExtensionId(kind, namespace, name); return { ...rest, + attachTo: resolveAttachTo(attachTo, namespace), $$type: '@backstage/Extension', version: internalDefinition.version, id, diff --git a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts index 8ffec893ee..b46bb426fa 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts @@ -19,7 +19,6 @@ import { Expand } from '@backstage/types'; import { ResolvedExtensionInput } from './createExtension'; import { createExtensionDataContainer } from '@internal/frontend'; import { - ExtensionDataRef, ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; @@ -28,16 +27,8 @@ import { ExtensionDataContainer } from './types'; /** @ignore */ export type ResolvedInputValueOverrides< - TInputs extends { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - } = { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; + TInputs extends { [inputName in string]: ExtensionInput } = { + [inputName in string]: ExtensionInput; }, > = Expand< { @@ -90,12 +81,7 @@ function expectItem(value: T | T[]): T { /** @internal */ export function resolveInputOverrides( - declaredInputs?: { - [inputName in string]: ExtensionInput< - ExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }, + declaredInputs?: { [inputName in string]: ExtensionInput }, inputs?: { [KName in string]?: | ({ node: AppNode } & ExtensionDataContainer) diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 1fa18a1696..3adc1d0f0b 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/frontend-test-utils +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/plugin-app@0.3.3-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/frontend-app-api@0.13.2 + - @backstage/config@1.3.6 + - @backstage/plugin-app@0.3.2 + - @backstage/test-utils@1.7.13 + ## 0.4.1-next.0 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 18ab54d197..48ed1b6827 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.4.1-next.0", + "version": "0.4.2-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index d49f70955d..60d231e612 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/integration-aws-node +## 0.1.19 + +### Patch Changes + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 0.1.19-next.0 ### Patch Changes diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index e3cd09cde0..0484a54e0a 100644 --- a/packages/integration-aws-node/package.json +++ b/packages/integration-aws-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-aws-node", - "version": "0.1.19-next.0", + "version": "0.1.19", "description": "Helpers for fetching AWS account credentials", "backstage": { "role": "node-library" diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 6956f04b08..cf30e3fadc 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/integration-react +## 1.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + +## 1.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.2 + - @backstage/core-plugin-api@1.12.0 + - @backstage/config@1.3.6 + ## 1.2.12-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index ca9dce00d9..d5cc25c5c2 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.2.12-next.0", + "version": "1.2.13-next.0", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration-react/report.api.md b/packages/integration-react/report.api.md index 78da2eb5fa..0974c4821a 100644 --- a/packages/integration-react/report.api.md +++ b/packages/integration-react/report.api.md @@ -3,17 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiFactory } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; import { AuthRequestOptions } from '@backstage/core-plugin-api'; -import { BackstageIdentityApi } from '@backstage/core-plugin-api'; +import { BackstageIdentityApi } from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { OAuthApi } from '@backstage/core-plugin-api'; -import { OpenIdConnectApi } from '@backstage/core-plugin-api'; -import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { OpenIdConnectApi } from '@backstage/frontend-plugin-api'; +import { ProfileInfoApi } from '@backstage/frontend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { SessionApi } from '@backstage/core-plugin-api'; +import { SessionApi } from '@backstage/frontend-plugin-api'; // @public export class ScmAuth implements ScmAuthApi { diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index bcc2a938ae..a45b3d9770 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/integration +## 1.18.3-next.0 + +### Patch Changes + +- e15fdae: Made the github urls case insensitive. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 1.18.2 + +### Patch Changes + +- fa255f5: Support for Bitbucket Cloud's API token was added as `appPassword` is deprecated (no new creation from September 9, 2025) and will be removed on June 9, 2026. + + API token usage example: + + ```yaml + integrations: + bitbucketCloud: + - username: user@domain.com + token: my-token + ``` + +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/config@1.3.6 + ## 1.18.2-next.0 ### Patch Changes diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 298d765697..a601d63472 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -176,10 +176,16 @@ export interface Config { */ username: string; /** - * Bitbucket Cloud app password used to authenticate requests. + * Token used to authenticate requests. * @visibility secret */ - appPassword: string; + token?: string; + /** + * Bitbucket Cloud app password used to authenticate requests. + * @visibility secret + * @deprecated Use `token` instead. + */ + appPassword?: string; /** * PGP signing key for signing commits. * @visibility secret diff --git a/packages/integration/package.json b/packages/integration/package.json index 45928cd59b..41eeb67e06 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.18.2-next.0", + "version": "1.18.3-next.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" @@ -51,7 +51,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", - "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, "configSchema": "config.d.ts" diff --git a/packages/integration/src/bitbucketCloud/config.test.ts b/packages/integration/src/bitbucketCloud/config.test.ts index 2c68059196..9488c8b5f7 100644 --- a/packages/integration/src/bitbucketCloud/config.test.ts +++ b/packages/integration/src/bitbucketCloud/config.test.ts @@ -22,54 +22,58 @@ import { readBitbucketCloudIntegrationConfigs, } from './config'; +// Mock constants +const BITBUCKET_CLOUD_HOST = 'bitbucket.org'; +const BITBUCKET_CLOUD_API_BASE_URL = 'https://api.bitbucket.org/2.0'; + +async function buildFrontendConfig( + data: Partial, +): Promise { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { bitbucketCloud: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader(processed[0].data as any); +} + describe('readBitbucketCloudIntegrationConfig', () => { function buildConfig(data: Partial): Config { return new ConfigReader(data); } - async function buildFrontendConfig( - data: Partial, - ): Promise { - const fullSchema = await loadConfigSchema({ - dependencies: ['@backstage/integration'], - }); - const serializedSchema = fullSchema.serialize() as { - schemas: { value: { properties?: { integrations?: object } } }[]; - }; - const schema = await loadConfigSchema({ - serialized: { - ...serializedSchema, // only include schemas that apply to integrations - schemas: serializedSchema.schemas.filter( - s => s.value?.properties?.integrations, - ), - }, - }); - const processed = schema.process( - [{ data: { integrations: { bitbucketCloud: [data] } }, context: 'app' }], - { visibility: ['frontend'] }, - ); - return new ConfigReader(processed[0].data as any); - } - it('reads all values', () => { const output = readBitbucketCloudIntegrationConfig( buildConfig({ username: 'u', - appPassword: '\n\n\np', + token: 't', }), ); expect(output).toEqual({ - apiBaseUrl: 'https://api.bitbucket.org/2.0', - appPassword: 'p', - host: 'bitbucket.org', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, username: 'u', + token: 't', }); }); it('rejects funky configs', () => { const valid: any = { username: 'u', - appPassword: 'p', + token: 't', }; expect(() => readBitbucketCloudIntegrationConfig( @@ -77,15 +81,13 @@ describe('readBitbucketCloudIntegrationConfig', () => { ), ).toThrow(/username/); expect(() => - readBitbucketCloudIntegrationConfig( - buildConfig({ ...valid, appPassword: 7 }), - ), - ).toThrow(/appPassword/); + readBitbucketCloudIntegrationConfig(buildConfig({ ...valid, token: 7 })), + ).toThrow(/token/); }); it('credentials hidden on the frontend', async () => { const frontendConfig = await buildFrontendConfig({ - appPassword: 'p', + token: 't', username: 'u', }); expect( @@ -95,11 +97,76 @@ describe('readBitbucketCloudIntegrationConfig', () => { ), ).toEqual([ { - apiBaseUrl: 'https://api.bitbucket.org/2.0', - host: 'bitbucket.org', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, }, ]); }); + + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + describe('handles deprecated appPassword', () => { + it('reads all values', () => { + const output = readBitbucketCloudIntegrationConfig( + buildConfig({ + appPassword: '\n\np', + username: 'u', + }), + ); + expect(output).toEqual({ + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + appPassword: 'p', + username: 'u', + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + appPassword: 'p', + username: 'u', + }; + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ ...valid, appPassword: 7 }), + ), + ).toThrow(/appPassword/); + }); + + it('rejects if misconfigured', () => { + const valid: any = { + appPassword: 'p', + token: 't', + username: 'u', + }; + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ ...valid, appPassword: undefined, token: undefined }), + ), + ).toThrow( + /must be configured with as username and either a token or an appPassword/, + ); + }); + + it('credentials hidden on the frontend', async () => { + const frontendConfig = await buildFrontendConfig({ + appPassword: 'p', + username: 'u', + }); + expect( + readBitbucketCloudIntegrationConfigs( + frontendConfig.getOptionalConfigArray( + 'integrations.bitbucketCloud', + ) ?? [], + ), + ).toEqual([ + { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + }, + ]); + }); + }); }); describe('readBitbucketCloudIntegrationConfigs', () => { @@ -114,15 +181,15 @@ describe('readBitbucketCloudIntegrationConfigs', () => { buildConfig([ { username: 'u', - appPassword: 'p', + token: 't', }, ]), ); expect(output).toContainEqual({ apiBaseUrl: 'https://api.bitbucket.org/2.0', - appPassword: 'p', host: 'bitbucket.org', username: 'u', + token: 't', }); }); diff --git a/packages/integration/src/bitbucketCloud/config.ts b/packages/integration/src/bitbucketCloud/config.ts index eb4b5c4904..0e08e95a79 100644 --- a/packages/integration/src/bitbucketCloud/config.ts +++ b/packages/integration/src/bitbucketCloud/config.ts @@ -49,6 +49,8 @@ export type BitbucketCloudIntegrationConfig = { /** * The access token to use for requests to Bitbucket Cloud (bitbucket.org). + * + * See https://support.atlassian.com/bitbucket-cloud/docs/api-tokens/ */ token?: string; @@ -70,13 +72,23 @@ export function readBitbucketCloudIntegrationConfig( // If config is provided, we assume authenticated access is desired // (as the anonymous one is provided by default). const username = config.getString('username'); - const appPassword = config.getString('appPassword')?.trim(); + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + const appPassword = config.getOptionalString('appPassword')?.trim(); + const token = config.getOptionalString('token'); + + if (!token && !appPassword) { + throw new Error( + `Bitbucket Cloud integration must be configured with as username and either a token or an appPassword.`, + ); + } return { host, apiBaseUrl, username, appPassword, + token, commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucketCloud/core.test.ts b/packages/integration/src/bitbucketCloud/core.test.ts index 56a53b924d..fa8fc2ae43 100644 --- a/packages/integration/src/bitbucketCloud/core.test.ts +++ b/packages/integration/src/bitbucketCloud/core.test.ts @@ -25,22 +25,38 @@ import { getBitbucketCloudRequestOptions, } from './core'; +// Mock constants +const BITBUCKET_CLOUD_HOST = 'bitbucket.org'; +const BITBUCKET_CLOUD_API_BASE_URL = 'https://api.bitbucket.org/2.0'; + describe('bitbucketCloud core', () => { const worker = setupServer(); registerMswTestHooks(worker); describe('getBitbucketCloudRequestOptions', () => { it('insert basic auth when needed', () => { + const withUsernameAndToken: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + username: 'some-user@domain.com', + token: 'my-token', + }; + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. const withUsernameAndPassword: BitbucketCloudIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, username: 'some-user', appPassword: 'my-secret', }; - const withoutUsernameAndPassword: BitbucketCloudIntegrationConfig = { - host: 'bitbucket.org', - apiBaseUrl: 'https://api.bitbucket.org/2.0', + const withoutUsername: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, }; + expect( + (getBitbucketCloudRequestOptions(withUsernameAndToken).headers as any) + .Authorization, + ).toEqual('Basic c29tZS11c2VyQGRvbWFpbi5jb206bXktdG9rZW4='); expect( ( getBitbucketCloudRequestOptions(withUsernameAndPassword) @@ -48,10 +64,8 @@ describe('bitbucketCloud core', () => { ).Authorization, ).toEqual('Basic c29tZS11c2VyOm15LXNlY3JldA=='); expect( - ( - getBitbucketCloudRequestOptions(withoutUsernameAndPassword) - .headers as any - ).Authorization, + (getBitbucketCloudRequestOptions(withoutUsername).headers as any) + .Authorization, ).toBeUndefined(); }); }); diff --git a/packages/integration/src/bitbucketCloud/core.ts b/packages/integration/src/bitbucketCloud/core.ts index 37feb5dad6..a97de872de 100644 --- a/packages/integration/src/bitbucketCloud/core.ts +++ b/packages/integration/src/bitbucketCloud/core.ts @@ -117,24 +117,28 @@ export function getBitbucketCloudFileFetchUrl( /** * Gets the request options necessary to make requests to a given provider. + * Returns headers for authenticating with Bitbucket Cloud. + * Supports both username/token and username/appPassword auth. * * @param config - The relevant provider config * @public */ export function getBitbucketCloudRequestOptions( config: BitbucketCloudIntegrationConfig, -): { headers: Record } { +): { + headers: Record; +} { const headers: Record = {}; - if (config.username && config.appPassword) { + // TODO: appPassword can be removed once fully + // deprecated by BitBucket on 9th June 2026. + if (config.username && (config.token ?? config.appPassword)) { const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, + `${config.username}:${config.token ?? config.appPassword}`, 'utf8', ); headers.Authorization = `Basic ${buffer.toString('base64')}`; } - return { - headers, - }; + return { headers }; } diff --git a/packages/integration/src/github/GithubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts index 70f1c4a16c..c54d4db2ef 100644 --- a/packages/integration/src/github/GithubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -134,4 +134,24 @@ describe('replaceGithubUrlType', () => { ), ).toBe('https://github.com/backstage/backstage/blob/tree/README.md'); }); + it('should replace with lowercase', () => { + expect( + replaceGithubUrlType( + 'https://githuB.com/backstage/backstage/blob/master/README.md', + 'edit', + ), + ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); + expect( + replaceGithubUrlType( + 'https://github.com/Backstage/backstage/blob/master/README.md', + 'edit', + ), + ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); + expect( + replaceGithubUrlType( + 'https://github.com/backstage/Backstage/blob/master/README.md', + 'edit', + ), + ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); + }); }); diff --git a/packages/integration/src/github/GithubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts index 880a2bed21..79d5e7f421 100644 --- a/packages/integration/src/github/GithubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -95,7 +95,9 @@ export function replaceGithubUrlType( return url.replace( /\/\/([^/]+)\/([^/]+)\/([^/]+)\/(blob|tree|edit)\//, (_, host, owner, repo) => { - return `//${host}/${owner}/${repo}/${type}/`; + return `//${host.toLocaleLowerCase('en-US')}/${owner.toLocaleLowerCase( + 'en-US', + )}/${repo.toLocaleLowerCase('en-US')}/${type}/`; }, ); } diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index c85546dd9d..36723eaecf 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/repo-tools +## 0.16.1-next.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- Updated dependencies + - @backstage/cli-node@0.2.16-next.1 + - @backstage/cli-common@0.1.16-next.1 + +## 0.16.1-next.0 + +### Patch Changes + +- 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/cli-node@0.2.16-next.0 + - @backstage/config-loader@1.10.7-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 0.16.0 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- 05f60e1: Refactored constructor parameter properties to explicit property declarations for compatibility with TypeScript's `erasableSyntaxOnly` setting. This internal refactoring maintains all existing functionality while ensuring TypeScript compilation compatibility. +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0 + - @backstage/config-loader@1.10.6 + - @backstage/cli-node@0.2.15 + - @backstage/catalog-model@1.7.6 + +## 0.16.0-next.2 + +### Minor Changes + +- 11c61f2: The `package-docs` command will now automatically use a `typedoc.json` file if one exists at the root of your project. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.2 + +## 0.15.4-next.1 + +### Patch Changes + +- 8f56eae: Updated knip-reports to detect dependencies in dev/alpha pattern +- Updated dependencies + - @backstage/backend-plugin-api@1.5.0-next.1 + ## 0.15.4-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index f3789e14c1..8fb56a229a 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.15.4-next.0", + "version": "0.16.1-next.1", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" @@ -51,8 +51,8 @@ "@backstage/errors": "workspace:^", "@electric-sql/pglite": "^0.3.0", "@manypkg/get-packages": "^1.1.3", - "@microsoft/api-documenter": "^7.25.7", - "@microsoft/api-extractor": "^7.47.2", + "@microsoft/api-documenter": "^7.28.1", + "@microsoft/api-extractor": "^7.55.1", "@openapitools/openapi-generator-cli": "^2.7.0", "@stoplight/spectral-core": "^1.18.0", "@stoplight/spectral-formatters": "^1.1.0", @@ -90,7 +90,7 @@ "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/is-glob": "^4.0.2", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/prettier": "^2.0.0", "typedoc": "^0.28.0" }, diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts index d7304553c0..a51c9ed17b 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { spawnSync } from 'child_process'; +import { run, ExitCodeError } from '@backstage/cli-common'; import { paths as cliPaths } from '../../../lib/paths'; /** @@ -30,21 +30,26 @@ import { paths as cliPaths } from '../../../lib/paths'; */ export async function generateTypeDeclarations(tsconfigFilePath: string) { await fs.remove(cliPaths.resolveTargetRoot('dist-types')); - const { status } = spawnSync( - 'yarn', - [ - 'tsc', - ['--project', tsconfigFilePath], - ['--skipLibCheck', 'false'], - ['--incremental', 'false'], - ].flat(), - { - stdio: 'inherit', - shell: true, - cwd: cliPaths.targetRoot, - }, - ); - if (status !== 0) { - process.exit(status || undefined); + try { + await run( + [ + 'yarn', + 'tsc', + '--project', + tsconfigFilePath, + '--skipLibCheck', + 'false', + '--incremental', + 'false', + ], + { + cwd: cliPaths.targetRoot, + }, + ).waitForExit(); + } catch (error) { + if (error instanceof ExitCodeError) { + process.exit(error.code); + } + throw error; } } diff --git a/packages/repo-tools/src/commands/generate-patch/generate-patch.ts b/packages/repo-tools/src/commands/generate-patch/generate-patch.ts index 10b31e0584..90cbe7f322 100644 --- a/packages/repo-tools/src/commands/generate-patch/generate-patch.ts +++ b/packages/repo-tools/src/commands/generate-patch/generate-patch.ts @@ -98,7 +98,9 @@ export default async ( throw new Error(`Could not find package ${packageArg} in source repo`); } - const tmpDir = await fs.mkdtemp(os.tmpdir()); + const tmpDir = await fs.mkdtemp( + joinPath(os.tmpdir(), 'backstage-repo-tools-generate-patch-'), + ); const ctx: PatchContext = { sourceRepo, targetRepo, diff --git a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts index 68bb0a9835..af3e32d2f4 100644 --- a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts +++ b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts @@ -58,7 +58,7 @@ async function generateKnipConfig({ knipConfigPath }: KnipConfigOptions) { workspaces: { '.': {}, '{packages,plugins}/*': { - entry: ['dev/index.{ts,tsx}', 'src/index.{ts,tsx}'], + entry: ['dev/**/*.{ts,tsx}', 'src/index.{ts,tsx}'], ignore: [ '.eslintrc.js', 'config.d.ts', diff --git a/packages/repo-tools/src/commands/package-docs/command.ts b/packages/repo-tools/src/commands/package-docs/command.ts index 174a1d949d..28255193f0 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -23,6 +23,7 @@ import { mkdirp } from 'fs-extra'; import { PackageDocsCache } from './Cache'; import { Lockfile } from '@backstage/cli-node'; import { glob } from 'glob'; +import { existsSync } from 'fs'; const limit = pLimit(8); @@ -220,6 +221,9 @@ export default async function packageDocs(paths: string[] = [], opts: any) { ...HIGHLIGHT_LANGUAGES.flatMap(e => ['--highlightLanguages', e]), '--out', cliPaths.resolveTargetRoot('type-docs'), + ...(existsSync(cliPaths.resolveTargetRoot('typedoc.json')) + ? ['--options', cliPaths.resolveTargetRoot('typedoc.json')] + : []), ].join(' '), { cwd: cliPaths.targetRoot, diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 8a8817c721..7402342607 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -14,21 +14,21 @@ * limitations under the License. */ +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import chalk from 'chalk'; +import fs from 'fs-extra'; import { resolve } from 'path'; +import { exec } from '../../../../../lib/exec'; import { OPENAPI_IGNORE_FILES, OUTPUT_PATH, } from '../../../../../lib/openapi/constants'; -import { paths as cliPaths } from '../../../../../lib/paths'; -import fs from 'fs-extra'; -import { exec } from '../../../../../lib/exec'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; +import { deduplicateImports } from '../../../../../lib/openapi/dedupe-imports'; import { getPathToCurrentOpenApiSpec, toGeneratorAdditionalProperties, } from '../../../../../lib/openapi/helpers'; -import { deduplicateImports } from '../../../../../lib/openapi/dedupe-imports'; +import { paths as cliPaths } from '../../../../../lib/paths'; async function generate( outputDirectory: string, diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts index 515d1d68ed..34c8589d54 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts @@ -68,6 +68,8 @@ async function lint(directoryPath: string, config?: { strict: boolean }) { 'operation-tags': 'off', 'hosts-https-only-oas3': 'off', 'no-unknown-error-format': 'off', + // Disabled as it currently gives false positives when 'example' is used as a property name. + 'oas3-valid-media-example': 'off', }, }, ], diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache index d01ea29260..d246837155 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelGeneric.mustache @@ -18,7 +18,7 @@ export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} * {{{.}}} */ {{/description}} - '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; {{/vars}} }{{#isNullable}} | null{{/isNullable}} diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache index a3f5e00904..3f5a3baf5e 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelTaggedUnion.mustache @@ -12,7 +12,7 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}} * {{{.}}} */ {{/description}} - {{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; {{/allVars}} } {{>modelGenericEnums}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache index d01ea29260..d246837155 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelGeneric.mustache @@ -18,7 +18,7 @@ export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} * {{{.}}} */ {{/description}} - '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; {{/vars}} }{{#isNullable}} | null{{/isNullable}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache index a3f5e00904..3f5a3baf5e 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelTaggedUnion.mustache @@ -12,7 +12,7 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}} * {{{.}}} */ {{/description}} - {{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; + '{{#baseName}}{{baseName}}{{/baseName}}{{^baseName}}{{name}}{{/baseName}}'{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; {{/allVars}} } {{>modelGenericEnums}} diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 3b3a6f7c8c..26f3232259 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/scaffolder +## 0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.0 + - @backstage/plugin-scaffolder-react@1.19.4-next.0 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.0 + - @backstage/plugin-scaffolder-react@1.19.3 + ## 0.0.15-next.0 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 256ec5b5e3..94ec12f591 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.15-next.0", + "version": "0.0.16-next.0", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index adc7acaab5..455f18d38f 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,74 @@ # techdocs-cli-embedded-app +## 0.2.115-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + - @backstage/cli@0.34.6-next.1 + - @backstage/plugin-techdocs@1.16.1-next.1 + +## 0.2.115-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.1-next.0 + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-techdocs@1.16.1-next.0 + - @backstage/plugin-catalog@1.32.1-next.0 + - @backstage/cli@0.34.6-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/core-components@0.18.4-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## 0.2.114 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0 + - @backstage/cli@0.34.5 + - @backstage/plugin-techdocs@1.16.0 + - @backstage/plugin-catalog@1.32.0 + - @backstage/core-components@0.18.3 + - @backstage/core-plugin-api@1.12.0 + - @backstage/core-app-api@1.19.2 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-techdocs-react@1.3.5 + - @backstage/app-defaults@1.7.2 + - @backstage/integration-react@1.2.12 + - @backstage/test-utils@1.7.13 + +## 0.2.114-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.2 + - @backstage/plugin-techdocs@1.16.0-next.1 + - @backstage/plugin-catalog@1.32.0-next.1 + - @backstage/cli@0.34.5-next.1 + - @backstage/core-components@0.18.3-next.1 + - @backstage/core-plugin-api@1.11.2-next.1 + - @backstage/core-app-api@1.19.2-next.1 + +## 0.2.114-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.9.0-next.1 + ## 0.2.114-next.0 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index fa2cdf9dfb..a8a3ba85f4 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.114-next.0", + "version": "0.2.115-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli-embedded-app/public/index.html b/packages/techdocs-cli-embedded-app/public/index.html index 18bb3f50a9..196c711417 100644 --- a/packages/techdocs-cli-embedded-app/public/index.html +++ b/packages/techdocs-cli-embedded-app/public/index.html @@ -4,6 +4,7 @@ + ({ }, })); -enum Themes { - LIGHT = 'light', - DARK = 'dark', -} +const Themes = { + LIGHT: 'light', + DARK: 'dark', +} as const; export const TechDocsThemeToggle = () => { const appThemeApi = useApi(appThemeApiRef); const classes = useStyles(); - const [theme, setTheme] = useState( + const [theme, setTheme] = useState<(typeof Themes)[keyof typeof Themes]>( appThemeApi.getActiveThemeId() === Themes.DARK ? Themes.DARK : Themes.LIGHT, ); diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 13413f46c8..7ab69cbd97 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,35 @@ # @techdocs/cli +## 1.10.3-next.1 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.1 + +## 1.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.0 + - @backstage/cli-common@0.1.16-next.0 + - @backstage/plugin-techdocs-node@1.13.10-next.0 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + +## 1.10.2 + +### Patch Changes + +- c2a2017: Fix for missing styles due to move to BUI. +- Updated dependencies + - @backstage/backend-defaults@0.13.1 + - @backstage/plugin-techdocs-node@1.13.9 + - @backstage/config@1.3.6 + - @backstage/catalog-model@1.7.6 + ## 1.10.1-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index bf8755612c..d8aff1bc89 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.10.1-next.0", + "version": "1.10.3-next.1", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" @@ -62,7 +62,7 @@ "@types/commander": "^2.12.2", "@types/fs-extra": "^11.0.0", "@types/http-proxy": "^1.17.4", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/serve-handler": "^6.1.0", "@types/webpack-env": "^1.15.3", "find-process": "^1.4.5", diff --git a/packages/techdocs-cli/src/commands/serve/mkdocs.ts b/packages/techdocs-cli/src/commands/serve/mkdocs.ts index 00c5e5f0ed..cd1072a88f 100644 --- a/packages/techdocs-cli/src/commands/serve/mkdocs.ts +++ b/packages/techdocs-cli/src/commands/serve/mkdocs.ts @@ -18,7 +18,7 @@ import { OptionValues } from 'commander'; import openBrowser from 'react-dev-utils/openBrowser'; import { createLogger } from '../../lib/utility'; import { runMkdocsServer } from '../../lib/mkdocsServer'; -import { LogFunc, waitForSignal } from '../../lib/run'; +import { RunOnOutput } from '@backstage/cli-common'; import { getMkdocsYml } from '@backstage/plugin-techdocs-node'; import fs from 'fs-extra'; import { checkIfDockerIsOperational } from './utils'; @@ -45,7 +45,7 @@ export default async function serveMkdocs(opts: OptionValues) { // We want to open browser only once based on a log. let boolOpenBrowserTriggered = false; - const logFunc: LogFunc = data => { + const logFunc: RunOnOutput = data => { // Sometimes the lines contain an unnecessary extra new line in between const logLines = data.toString().split('\n'); const logPrefix = opts.docker ? '[docker/mkdocs]' : '[mkdocs]'; @@ -74,18 +74,18 @@ export default async function serveMkdocs(opts: OptionValues) { // Had me questioning this whole implementation for half an hour. // Commander stores --no-docker in cmd.docker variable - const childProcess = await runMkdocsServer({ + const childProcess = runMkdocsServer({ port: opts.port, dockerImage: opts.dockerImage, dockerEntrypoint: opts.dockerEntrypoint, dockerOptions: opts.dockerOption, useDocker: opts.docker, - stdoutLogFunc: logFunc, - stderrLogFunc: logFunc, + onStdout: logFunc, + onStderr: logFunc, }); // Keep waiting for user to cancel the process - await waitForSignal([childProcess]); + await childProcess.waitForExit(); if (configIsTemporary) { process.on('exit', async () => { diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 27f432c22d..a0d0030c6b 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -17,10 +17,9 @@ import { OptionValues } from 'commander'; import path from 'path'; import openBrowser from 'react-dev-utils/openBrowser'; -import { findPaths } from '@backstage/cli-common'; +import { findPaths, RunOnOutput } from '@backstage/cli-common'; import HTTPServer from '../../lib/httpServer'; import { runMkdocsServer } from '../../lib/mkdocsServer'; -import { LogFunc, waitForSignal } from '../../lib/run'; import { createLogger } from '../../lib/utility'; import { getMkdocsYml } from '@backstage/plugin-techdocs-node'; import fs from 'fs-extra'; @@ -83,7 +82,7 @@ export default async function serve(opts: OptionValues) { } let mkdocsServerHasStarted = false; - const mkdocsLogFunc: LogFunc = data => { + const mkdocsLogFunc: RunOnOutput = data => { // Sometimes the lines contain an unnecessary extra new line const logLines = data.toString().split('\n'); const logPrefix = opts.docker ? '[docker/mkdocs]' : '[mkdocs]'; @@ -107,14 +106,14 @@ export default async function serve(opts: OptionValues) { // https://github.com/mkdocs/mkdocs/issues/879#issuecomment-203536006 // Had me questioning this whole implementation for half an hour. logger.info('Starting mkdocs server.'); - const mkdocsChildProcess = await runMkdocsServer({ + const mkdocsChildProcess = runMkdocsServer({ port: opts.mkdocsPort, dockerImage: opts.dockerImage, dockerEntrypoint: opts.dockerEntrypoint, dockerOptions: opts.dockerOption, useDocker: opts.docker, - stdoutLogFunc: mkdocsLogFunc, - stderrLogFunc: mkdocsLogFunc, + onStdout: mkdocsLogFunc, + onStderr: mkdocsLogFunc, mkdocsConfigFileName: mkdocsYmlPath, mkdocsParameterClean: opts.mkdocsParameterClean, mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyreload, @@ -161,7 +160,7 @@ export default async function serve(opts: OptionValues) { ); }); - await waitForSignal([mkdocsChildProcess]); + await mkdocsChildProcess.waitForExit(); if (configIsTemporary) { process.on('exit', async () => { diff --git a/packages/techdocs-cli/src/commands/serve/utils.ts b/packages/techdocs-cli/src/commands/serve/utils.ts index 2b19b0fbc0..9ecb8bc29b 100644 --- a/packages/techdocs-cli/src/commands/serve/utils.ts +++ b/packages/techdocs-cli/src/commands/serve/utils.ts @@ -14,25 +14,22 @@ * limitations under the License. */ -import { promisify } from 'util'; import * as winston from 'winston'; -import { execFile } from 'child_process'; +import { runCheck } from '@backstage/cli-common'; export async function checkIfDockerIsOperational( logger: winston.Logger, ): Promise { logger.info('Checking Docker status...'); - try { - const runCheck = promisify(execFile); - await runCheck('docker', ['info'], { shell: true }); + const isOperational = await runCheck(['docker', 'info']); + if (isOperational) { logger.info( 'Docker is up and running. Proceed to starting up mkdocs server', ); return true; - } catch { - logger.error( - 'Docker is not running. Exiting. Please check status of Docker daemon with `docker info` before re-running', - ); - return false; } + logger.error( + 'Docker is not running. Exiting. Please check status of Docker daemon with `docker info` before re-running', + ); + return false; } diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts index 83f9c9f382..5acd1784a3 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts @@ -15,9 +15,9 @@ */ import { runMkdocsServer } from './mkdocsServer'; -import { run } from './run'; +import { run } from '@backstage/cli-common'; -jest.mock('./run', () => { +jest.mock('@backstage/cli-common', () => { return { run: jest.fn(), }; @@ -29,12 +29,12 @@ describe('runMkdocsServer', () => { }); describe('docker', () => { - it('should run docker directly by default', async () => { - await runMkdocsServer({}); + it('should run docker directly by default', () => { + runMkdocsServer({}); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'run', `${process.cwd()}:/content`, '8000:8000', @@ -47,26 +47,24 @@ describe('runMkdocsServer', () => { ); }); - it('should accept port option', async () => { - await runMkdocsServer({ port: '5678' }); + it('should accept port option', () => { + runMkdocsServer({ port: '5678' }); expect(run).toHaveBeenCalledWith( - 'docker', - expect.arrayContaining(['5678:5678', '0.0.0.0:5678']), + expect.arrayContaining(['docker', '5678:5678', '0.0.0.0:5678']), expect.objectContaining({}), ); }); - it('should accept custom docker image', async () => { - await runMkdocsServer({ dockerImage: 'my-org/techdocs' }); + it('should accept custom docker image', () => { + runMkdocsServer({ dockerImage: 'my-org/techdocs' }); expect(run).toHaveBeenCalledWith( - 'docker', - expect.arrayContaining(['my-org/techdocs']), + expect.arrayContaining(['docker', 'my-org/techdocs']), expect.objectContaining({}), ); }); - it('should accept custom docker options', async () => { - await runMkdocsServer({ + it('should accept custom docker options', () => { + runMkdocsServer({ dockerOptions: [ '--add-host=internal.host:192.168.11.12', '--name', @@ -75,8 +73,8 @@ describe('runMkdocsServer', () => { }); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'run', '--rm', '-w', @@ -98,14 +96,14 @@ describe('runMkdocsServer', () => { ); }); - it('should accept additinoal mkdocs CLI parameters', async () => { - await runMkdocsServer({ + it('should accept additinoal mkdocs CLI parameters', () => { + runMkdocsServer({ mkdocsParameterClean: true, mkdocsParameterStrict: true, }); expect(run).toHaveBeenCalledWith( - 'docker', expect.arrayContaining([ + 'docker', 'serve', '--dev-addr', '0.0.0.0:8000', @@ -118,21 +116,24 @@ describe('runMkdocsServer', () => { }); describe('mkdocs', () => { - it('should run mkdocs if specified', async () => { - await runMkdocsServer({ useDocker: false }); + it('should run mkdocs if specified', () => { + runMkdocsServer({ useDocker: false }); expect(run).toHaveBeenCalledWith( - 'mkdocs', - expect.arrayContaining(['serve', '--dev-addr', '127.0.0.1:8000']), + expect.arrayContaining([ + 'mkdocs', + 'serve', + '--dev-addr', + '127.0.0.1:8000', + ]), expect.objectContaining({}), ); }); - it('should accept port option', async () => { - await runMkdocsServer({ useDocker: false, port: '5678' }); + it('should accept port option', () => { + runMkdocsServer({ useDocker: false, port: '5678' }); expect(run).toHaveBeenCalledWith( - 'mkdocs', - expect.arrayContaining(['127.0.0.1:5678']), + expect.arrayContaining(['mkdocs', '127.0.0.1:5678']), expect.objectContaining({}), ); }); diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.ts b/packages/techdocs-cli/src/lib/mkdocsServer.ts index a793643367..c557015256 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.ts @@ -14,30 +14,29 @@ * limitations under the License. */ -import { ChildProcess } from 'child_process'; -import { run, LogFunc } from './run'; +import { run, RunChildProcess, RunOnOutput } from '@backstage/cli-common'; -export const runMkdocsServer = async (options: { +export const runMkdocsServer = (options: { port?: string; useDocker?: boolean; dockerImage?: string; dockerEntrypoint?: string; dockerOptions?: string[]; - stdoutLogFunc?: LogFunc; - stderrLogFunc?: LogFunc; + onStdout?: RunOnOutput; + onStderr?: RunOnOutput; mkdocsConfigFileName?: string; mkdocsParameterClean?: boolean; mkdocsParameterDirtyReload?: boolean; mkdocsParameterStrict?: boolean; -}): Promise => { +}): RunChildProcess => { const port = options.port ?? '8000'; const useDocker = options.useDocker ?? true; const dockerImage = options.dockerImage ?? 'spotify/techdocs'; if (useDocker) { - return await run( - 'docker', + return run( [ + 'docker', 'run', '--rm', '-w', @@ -63,15 +62,15 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { - stdoutLogFunc: options.stdoutLogFunc, - stderrLogFunc: options.stderrLogFunc, + onStdout: options.onStdout, + onStderr: options.onStderr, }, ); } - return await run( - 'mkdocs', + return run( [ + 'mkdocs', 'serve', '--dev-addr', `127.0.0.1:${port}`, @@ -83,8 +82,8 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { - stdoutLogFunc: options.stdoutLogFunc, - stderrLogFunc: options.stderrLogFunc, + onStdout: options.onStdout, + onStderr: options.onStderr, }, ); }; diff --git a/packages/techdocs-cli/src/lib/run.ts b/packages/techdocs-cli/src/lib/run.ts deleted file mode 100644 index 13ac7d9d4c..0000000000 --- a/packages/techdocs-cli/src/lib/run.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { spawn, SpawnOptions, ChildProcess } from 'child_process'; - -export type LogFunc = (data: Buffer | string) => void; -type SpawnOptionsPartialEnv = Omit & { - env?: Partial; - // Pipe stdout to this log function - stdoutLogFunc?: LogFunc; - // Pipe stderr to this log function - stderrLogFunc?: LogFunc; -}; - -// TODO: Accept log functions to pipe logs with. -// Runs a child command, returning the child process instance. -// Use it along with waitForSignal to run a long running process e.g. mkdocs serve -export const run = async ( - name: string, - args: string[] = [], - options: SpawnOptionsPartialEnv = {}, -): Promise => { - const { stdoutLogFunc, stderrLogFunc } = options; - - const env: NodeJS.ProcessEnv = { - ...process.env, - FORCE_COLOR: 'true', - ...(options.env ?? {}), - }; - - // Refer: https://nodejs.org/api/child_process.html#child_process_subprocess_stdio - const stdio = [ - 'inherit', - stdoutLogFunc ? 'pipe' : 'inherit', - stderrLogFunc ? 'pipe' : 'inherit', - ] as ('inherit' | 'pipe')[]; - - const childProcess = spawn(name, args, { - stdio: stdio, - ...options, - env, - }); - - if (stdoutLogFunc && childProcess.stdout) { - childProcess.stdout.on('data', stdoutLogFunc); - } - if (stderrLogFunc && childProcess.stderr) { - childProcess.stderr.on('data', stderrLogFunc); - } - - return childProcess; -}; - -// Block indefinitely and wait for a signal to stop the child process(es) -// Throw error if any child process errors -// Resolves only when all processes exit with status code 0 -export async function waitForSignal( - childProcesses: Array, -): Promise { - const promises: Array> = []; - - for (const signal of ['SIGINT', 'SIGTERM'] as const) { - process.on(signal, () => { - childProcesses.forEach(childProcess => { - childProcess.kill(); - }); - }); - } - - childProcesses.forEach(childProcess => { - if (typeof childProcess.exitCode === 'number') { - if (childProcess.exitCode) { - throw new Error(`Non zero exit code from child process`); - } - return; - } - - promises.push( - new Promise((resolve, reject) => { - childProcess.once('error', reject); - childProcess.once('exit', resolve); - }), - ); - }); - - await Promise.all(promises); -} diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 7beeed27c1..0d2d782969 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/test-utils +## 1.7.14-next.0 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.7.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.0 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/core-app-api@1.19.2 + - @backstage/plugin-permission-react@0.4.38 + - @backstage/config@1.3.6 + ## 1.7.13-next.0 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index b40fa8a0d8..e14805b620 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.13-next.0", + "version": "1.7.14-next.0", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index 344679355a..2989207743 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -25,7 +25,7 @@ import ObservableImpl from 'zen-observable'; import { Observable } from '@backstage/types'; // Internal import to avoid code duplication, this will lead to duplication in build output // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; +import { toInternalTranslationRef } from '../../../../../frontend-plugin-api/src/translation/TranslationRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { JsxInterpolator } from '../../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 1b5b158102..3eb2340d47 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.7.1-next.0 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + ## 0.7.0 ### Minor Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index cb7d5c12ce..cc0fe2237a 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.7.0", + "version": "0.7.1-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/packages/theme/report.api.md b/packages/theme/report.api.md index 18ac96ecec..f4e31db8e9 100644 --- a/packages/theme/report.api.md +++ b/packages/theme/report.api.md @@ -460,5 +460,6 @@ export interface UnifiedThemeProviderProps { children: ReactNode; // (undocumented) theme: UnifiedTheme; + themeName?: string; } ``` diff --git a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx index 85a47dbb1e..52769b65a3 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx @@ -20,11 +20,17 @@ import { } from '@material-ui/core/styles'; import { useTheme as useV5Theme } from '@mui/material/styles'; import { makeStyles as makeV5Styles } from '@mui/styles'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { UnifiedThemeProvider } from './UnifiedThemeProvider'; import { themes } from './themes'; describe('UnifiedThemeProvider', () => { + afterEach(() => { + document.body.removeAttribute('data-theme-name'); + document.body.removeAttribute('data-theme-mode'); + document.body.removeAttribute('data-unified-theme-stack'); + }); + it('provides a themes for v4 and v5 directly', () => { function MyV4Component() { const theme = useV4Theme(); @@ -81,4 +87,17 @@ describe('UnifiedThemeProvider', () => { 'rgb(158, 158, 158)', ); }); + + it('applies theme attributes based on the provided options', async () => { + render( + + theme + , + ); + + await waitFor(() => + expect(document.body.getAttribute('data-theme-name')).toBe('aperture'), + ); + expect(document.body.getAttribute('data-theme-mode')).toBe('light'); + }); }); diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index d0886cd146..69d843ed3e 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -37,6 +37,8 @@ import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material export interface UnifiedThemeProviderProps { children: ReactNode; theme: UnifiedTheme; + /** Optional override for the value written to the `data-theme-name` attribute. */ + themeName?: string; } /** @@ -66,15 +68,14 @@ import { useApplyThemeAttributes } from './useApplyThemeAttributes'; export function UnifiedThemeProvider( props: UnifiedThemeProviderProps, ): JSX.Element { - const { children, theme } = props; + const { children, theme, themeName } = props; const v4Theme = theme.getTheme('v4') as Mui4Theme; const v5Theme = theme.getTheme('v5') as Mui5Theme; - useApplyThemeAttributes( - v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode, - 'backstage', - ); + const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode; + + useApplyThemeAttributes(themeMode, themeName ?? 'backstage'); let result = children as JSX.Element; diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 2eec31def7..9f867ba2ed 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,417 @@ # @backstage/ui +## 0.10.0-next.1 + +### 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 + +- 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 + +## 0.9.1-next.0 + +### Patch Changes + +- 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 + +- 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 + +## 0.9.0 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + Migration: + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + Migration examples: + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. +- a67670d: **BREAKING**: Removed central `componentDefinitions` object and related type utilities (`ComponentDefinitionName`, `ComponentClassNames`). + + Component definitions are primarily intended for documenting the CSS class API for theming purposes, not for programmatic use in JavaScript/TypeScript. + + **Migration Guide:** + + If you were using component definitions or class names to build custom components, we recommend migrating to either: + + - Use Backstage UI components directly as building blocks, or + - Duplicate the component CSS in your own stylesheets instead of relying on internal class names + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. +- 26c6a78: Fix default text color in Backstage UI +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. +- 1059f95: Improved the Link component structure in Backstage UI. +- 836b0c7: Fixed dialog backdrop appearance in dark mode. +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- dac851f: Fix the default font size in Backstage UI. +- 3c0ea67: Fix CSS layer ordering in Backstage UI to make sure component styles are loaded after tokens and base declarations. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. +- 4eb455c: Fix font smoothing as default in Backstage UI. +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the `href` prop. +- e16ece5: Set the color-scheme property depending on theme +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. +- 00bfb83: Fix default font wight and font family in Backstage UI. + +## 0.9.0-next.3 + +### Minor Changes + +- 83c100e: **BREAKING**: Removed `Collapsible` component. Migrate to `Accordion` or use React Aria `Disclosure`. + + ## Migration Path 1: Accordion (Opinionated Styled Component) + + Accordion provides preset styling with a similar component structure. + + ```diff + - import { Collapsible } from '@backstage/ui'; + + import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + - + - } /> + - Content + - + + + + + + + Content + + + ``` + + CSS classes: `.bui-CollapsibleRoot` → `.bui-Accordion`, `.bui-CollapsibleTrigger` → `.bui-AccordionTrigger` (now on heading element), `.bui-CollapsiblePanel` → `.bui-AccordionPanel` + + ## Migration Path 2: React Aria Disclosure (Full Customization) + + For custom styling without preset styles: + + ```tsx + import { Disclosure, Button, DisclosurePanel } from 'react-aria-components'; + + + + Content + ; + ``` + +- 816af0f: **BREAKING**: The `SelectProps` interface now accepts a generic type parameter for selection mode. + + Added searchable and multiple selection support to Select component. The component now accepts `searchable`, `selectionMode`, and `searchPlaceholder` props to enable filtering and multi-selection modes. + + Migration: If you're using `SelectProps` type directly, update from `SelectProps` to `SelectProps<'single' | 'multiple'>`. Component usage remains backward compatible. + +### Patch Changes + +- 35a3614: Fixed CSS issues in Select component including popover width constraints, focus outline behavior, and overflow handling. +- 01476f0: Improved visual consistency of PasswordField, SearchField, and MenuAutocomplete components. +- 836b0c7: Fixed dialog backdrop appearance in dark mode. +- 6d35a6b: Removed `@base-ui-components/react` dependency as all components now use React Aria Components. +- 7839e7b: Added `loading` prop to Button and ButtonIcon components for displaying spinner during async operations. +- a00fb88: Fixed Table Row component to properly support opening links in new tabs via right-click or Cmd+Click when using the href prop. + +## 0.9.0-next.2 + +### Minor Changes + +- 539cf26: **BREAKING**: Migrated Avatar component from Base UI to custom implementation with size changes: + + - Base UI-specific props are no longer supported + - Size values have been updated: + - New `x-small` size added (1.25rem / 20px) + - `small` size unchanged (1.5rem / 24px) + - `medium` size unchanged (2rem / 32px, default) + - `large` size **changed from 3rem to 2.5rem** (40px) + - New `x-large` size added (3rem / 48px) + + Migration: + + ```diff + # Remove Base UI-specific props + - + + + + # Update large size usage to x-large for same visual size + - + + + ``` + + Added `purpose` prop for accessibility control (`'informative'` or `'decoration'`). + +- 134151f: Fixing styles on SearchField in Backstage UI after migration to CSS modules. `SearchField` has now its own set of class names. We previously used class names from `TextField` but this approach was creating some confusion so going forward in your theme you'll be able to theme `TextField` and `SearchField` separately. + +### Patch Changes + +- d01de00: Fix broken external links in Backstage UI Header component. +- deaa427: Fixed Text component to prevent `truncate` prop from being spread to the underlying DOM element. +- 1059f95: Improved the Link component structure in Backstage UI. +- 6874094: Migrated CellProfile component from Base UI Avatar to Backstage UI Avatar component. +- 719d772: Avatar components in x-small and small sizes now display only one initial instead of two, improving readability at smaller dimensions. +- 3b18d80: Fixed RadioGroup radio button ellipse distortion by preventing flex shrink and grow. +- e16ece5: Set the color-scheme property depending on theme + +## 0.9.0-next.1 + +### Minor Changes + +- 5c614ff: **BREAKING**: Migrated Checkbox component from Base UI to React Aria Components. + + API changes required: + + - `checked` → `isSelected` + - `defaultChecked` → `defaultSelected` + - `disabled` → `isDisabled` + - `required` → `isRequired` + - `label` prop removed - use `children` instead + - CSS: `bui-CheckboxLabel` class removed + - Data attribute: `data-checked` → `data-selected` + - Use without label is no longer supported + + Migration examples: + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accept terms + + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + Option + ``` + + Before: + + ```tsx + + ``` + + After: + + ```tsx + + Accessible label + + ``` + +- b78fc45: **BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + + Affected components: + + - Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator + - Switch + - Skeleton + - FieldLabel + - Header, HeaderToolbar + - HeaderPage + - Tabs, TabList, Tab, TabPanel + + If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. + +### Patch Changes + +- ff9f0c3: Enable tree-shaking of imports other than `*.css`. +- 1ef3ca4: Added new VisuallyHidden component for hiding content visually while keeping it accessible to screen readers. + ## 0.8.2-next.0 ### Patch Changes diff --git a/packages/ui/README.md b/packages/ui/README.md index 1a0fb84a0c..271b78fca4 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -16,3 +16,26 @@ yarn add @backstage/ui - [Backstage UI Documentation](https://ui.backstage.io) - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) - [Backstage Documentation](https://backstage.io/docs) + +## Writing Changesets for Components + +When creating changesets for component-specific changes, add component metadata to help maintain documentation: + +```markdown +--- +'@backstage/ui': patch +--- + +Fixed size prop handling for Avatar component. + +Affected components: Avatar +``` + +**Guidelines:** + +- **Component names**: Use PascalCase as they appear in imports (Avatar, ButtonIcon, SearchField) +- **Multiple components**: `Affected components: Button, ButtonLink, ButtonIcon` +- **General changes**: Omit the metadata line (build changes, package-level updates) +- **Placement**: The line can appear anywhere in the description + +The changelog sync tool will parse these tags and update the documentation site automatically. diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index 862de8e0e3..c96eda05c9 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -247,6 +247,14 @@ -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; diff --git a/packages/ui/package.json b/packages/ui/package.json index 10fb72840f..3ac6e991c8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/ui", - "version": "0.8.2-next.0", + "version": "0.10.0-next.1", "backstage": { "role": "web-library" }, @@ -19,7 +19,9 @@ "directory": "packages/ui" }, "license": "Apache-2.0", - "sideEffects": true, + "sideEffects": [ + "*.css" + ], "main": "src/index.ts", "types": "src/index.ts", "files": [ @@ -38,7 +40,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@base-ui-components/react": "1.0.0-alpha.7", "@remixicon/react": "^4.6.0", "@tanstack/react-table": "^8.21.3", "clsx": "^2.1.1", diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 91d81e29e3..0a7e289d0a 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -3,15 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar'; import { ButtonProps as ButtonProps_2 } from 'react-aria-components'; import { CellProps as CellProps_2 } from 'react-aria-components'; -import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible'; +import { CheckboxProps as CheckboxProps_2 } from 'react-aria-components'; import { ColumnProps as ColumnProps_2 } from 'react-aria-components'; import { ComponentProps } from 'react'; import type { ComponentPropsWithRef } from 'react'; import { DetailedHTMLProps } from 'react'; import type { DialogTriggerProps as DialogTriggerProps_2 } from 'react-aria-components'; +import type { DisclosureGroupProps } from 'react-aria-components'; +import type { DisclosurePanelProps } from 'react-aria-components'; +import type { DisclosureProps } from 'react-aria-components'; import type { ElementType } from 'react'; import { ForwardRefExoticComponent } from 'react'; import type { HeadingProps } from 'react-aria-components'; @@ -51,22 +53,96 @@ import type { TextFieldProps as TextFieldProps_2 } from 'react-aria-components'; import { TooltipProps as TooltipProps_2 } from 'react-aria-components'; import { TooltipTriggerComponentProps } from 'react-aria-components'; +// @public (undocumented) +export const Accordion: ForwardRefExoticComponent< + AccordionProps & RefAttributes +>; + +// @public +export const AccordionDefinition: { + readonly classNames: { + readonly root: 'bui-Accordion'; + readonly trigger: 'bui-AccordionTrigger'; + readonly triggerButton: 'bui-AccordionTriggerButton'; + readonly triggerTitle: 'bui-AccordionTriggerTitle'; + readonly triggerSubtitle: 'bui-AccordionTriggerSubtitle'; + readonly triggerIcon: 'bui-AccordionTriggerIcon'; + readonly panel: 'bui-AccordionPanel'; + readonly group: 'bui-AccordionGroup'; + }; +}; + +// @public (undocumented) +export const AccordionGroup: ForwardRefExoticComponent< + AccordionGroupProps & RefAttributes +>; + +// @public +export interface AccordionGroupProps extends DisclosureGroupProps { + allowsMultiple?: boolean; + // (undocumented) + className?: string; +} + +// @public (undocumented) +export const AccordionPanel: ForwardRefExoticComponent< + AccordionPanelProps & RefAttributes +>; + +// @public +export interface AccordionPanelProps extends DisclosurePanelProps { + // (undocumented) + className?: string; +} + +// @public +export interface AccordionProps extends DisclosureProps { + // (undocumented) + className?: string; +} + +// @public (undocumented) +export const AccordionTrigger: ForwardRefExoticComponent< + AccordionTriggerProps & RefAttributes +>; + +// @public +export interface AccordionTriggerProps extends HeadingProps { + // (undocumented) + children?: React.ReactNode; + // (undocumented) + className?: string; + // (undocumented) + subtitle?: string; + // (undocumented) + title?: string; +} + // @public (undocumented) export type AlignItems = 'stretch' | 'start' | 'center' | 'end'; // @public (undocumented) export const Avatar: ForwardRefExoticComponent< - AvatarProps & RefAttributes + AvatarProps & RefAttributes >; +// @public +export const AvatarDefinition: { + readonly classNames: { + readonly root: 'bui-AvatarRoot'; + readonly image: 'bui-AvatarImage'; + readonly fallback: 'bui-AvatarFallback'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + }; +}; + // @public (undocumented) -export interface AvatarProps - extends React.ComponentPropsWithoutRef { - // (undocumented) +export interface AvatarProps extends React.ComponentPropsWithoutRef<'div'> { name: string; - // (undocumented) - size?: 'small' | 'medium' | 'large'; - // (undocumented) + purpose?: 'decoration' | 'informative'; + size?: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; src: string; } @@ -89,6 +165,37 @@ export const Box: ForwardRefExoticComponent< BoxProps & RefAttributes >; +// @public +export const BoxDefinition: { + readonly classNames: { + readonly root: 'bui-Box'; + }; + readonly utilityProps: [ + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + 'position', + 'display', + 'width', + 'minWidth', + 'maxWidth', + 'height', + 'minHeight', + 'maxHeight', + ]; +}; + // @public (undocumented) export interface BoxProps extends SpaceProps { // (undocumented) @@ -127,16 +234,41 @@ export const Button: ForwardRefExoticComponent< ButtonProps & RefAttributes >; +// @public +export const ButtonDefinition: { + readonly classNames: { + readonly root: 'bui-Button'; + readonly content: 'bui-ButtonContent'; + readonly spinner: 'bui-ButtonSpinner'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + readonly variant: readonly ['primary', 'secondary', 'tertiary']; + readonly loading: readonly [true, false]; + }; +}; + // @public (undocumented) export const ButtonIcon: ForwardRefExoticComponent< ButtonIconProps & RefAttributes >; +// @public +export const ButtonIconDefinition: { + readonly classNames: { + readonly root: 'bui-ButtonIcon'; + readonly content: 'bui-ButtonIconContent'; + readonly spinner: 'bui-ButtonIconSpinner'; + }; +}; + // @public export interface ButtonIconProps extends ButtonProps_2 { // (undocumented) icon?: ReactElement; // (undocumented) + loading?: boolean; + // (undocumented) size?: 'small' | 'medium' | Partial>; // (undocumented) variant?: @@ -151,6 +283,13 @@ export const ButtonLink: ForwardRefExoticComponent< ButtonLinkProps & RefAttributes >; +// @public +export const ButtonLinkDefinition: { + readonly classNames: { + readonly root: 'bui-ButtonLink'; + }; +}; + // @public export interface ButtonLinkProps extends LinkProps_2 { // (undocumented) @@ -178,6 +317,8 @@ export interface ButtonProps extends ButtonProps_2 { // (undocumented) iconStart?: ReactElement; // (undocumented) + loading?: boolean; + // (undocumented) size?: 'small' | 'medium' | Partial>; // (undocumented) variant?: @@ -203,6 +344,16 @@ export interface CardBodyProps extends React.HTMLAttributes { children?: React.ReactNode; } +// @public +export const CardDefinition: { + readonly classNames: { + readonly root: 'bui-Card'; + readonly header: 'bui-CardHeader'; + readonly body: 'bui-CardBody'; + readonly footer: 'bui-CardFooter'; + }; +}; + // @public export const CardFooter: ForwardRefExoticComponent< CardFooterProps & RefAttributes @@ -243,7 +394,7 @@ export const CellProfile: (props: CellProfileProps) => JSX_2.Element; // @public (undocumented) export interface CellProfileProps extends CellProps_2 { // (undocumented) - color?: 'primary' | 'secondary'; + color?: TextColors; // (undocumented) description?: string; // (undocumented) @@ -255,9 +406,18 @@ export interface CellProfileProps extends CellProps_2 { } // @public (undocumented) -export interface CellProps extends CellProps_2 { +export interface CellProps extends CellProps_2 {} + +// @public (undocumented) +export const CellText: { + (props: CellTextProps): JSX_2.Element; + displayName: string; +}; + +// @public (undocumented) +export interface CellTextProps extends CellProps_2 { // (undocumented) - color?: 'primary' | 'secondary'; + color?: TextColors; // (undocumented) description?: string; // (undocumented) @@ -270,55 +430,29 @@ export interface CellProps extends CellProps_2 { // @public (undocumented) export const Checkbox: ForwardRefExoticComponent< - CheckboxProps & RefAttributes + CheckboxProps & RefAttributes >; +// @public +export const CheckboxDefinition: { + readonly classNames: { + readonly root: 'bui-Checkbox'; + readonly indicator: 'bui-CheckboxIndicator'; + }; + readonly dataAttributes: { + readonly selected: readonly [true, false]; + }; +}; + // @public (undocumented) -export interface CheckboxProps { +export interface CheckboxProps extends CheckboxProps_2 { // (undocumented) - checked?: boolean; - // (undocumented) - className?: string; - // (undocumented) - defaultChecked?: boolean; - // (undocumented) - disabled?: boolean; - // (undocumented) - label?: string; - // (undocumented) - name?: string; - // (undocumented) - onChange?: (checked: boolean) => void; - // (undocumented) - required?: boolean; - // (undocumented) - style?: React.CSSProperties; - // (undocumented) - value?: string; + children: React.ReactNode; } // @public export type ClassNamesMap = Record; -// @public -export const Collapsible: { - Root: ForwardRefExoticComponent< - Omit, 'ref'> & - RefAttributes - >; - Trigger: ForwardRefExoticComponent< - Omit< - Collapsible_2.Trigger.Props & RefAttributes, - 'ref' - > & - RefAttributes - >; - Panel: ForwardRefExoticComponent< - Omit, 'ref'> & - RefAttributes - >; -}; - // @public (undocumented) export const Column: (props: ColumnProps) => JSX_2.Element; @@ -344,10 +478,6 @@ export type Columns = | '12' | 'auto'; -// @public -export type ComponentClassNames = - (typeof componentDefinitions)[T]['classNames']; - // @public export interface ComponentDefinition { // (undocumented) @@ -358,398 +488,19 @@ export interface ComponentDefinition { utilityProps?: string[]; } -// @public -export type ComponentDefinitionName = keyof typeof componentDefinitions; - -// @public -export const componentDefinitions: { - readonly Avatar: { - readonly classNames: { - readonly root: 'bui-AvatarRoot'; - readonly image: 'bui-AvatarImage'; - readonly fallback: 'bui-AvatarFallback'; - }; - readonly dataAttributes: { - readonly size: readonly ['small', 'medium', 'large']; - }; - }; - readonly Box: { - readonly classNames: { - readonly root: 'bui-Box'; - }; - readonly utilityProps: [ - 'm', - 'mb', - 'ml', - 'mr', - 'mt', - 'mx', - 'my', - 'p', - 'pb', - 'pl', - 'pr', - 'pt', - 'px', - 'py', - 'position', - 'display', - 'width', - 'minWidth', - 'maxWidth', - 'height', - 'minHeight', - 'maxHeight', - ]; - }; - readonly Button: { - readonly classNames: { - readonly root: 'bui-Button'; - }; - readonly dataAttributes: { - readonly size: readonly ['small', 'medium', 'large']; - readonly variant: readonly ['primary', 'secondary', 'tertiary']; - }; - }; - readonly ButtonIcon: { - readonly classNames: { - readonly root: 'bui-ButtonIcon'; - }; - }; - readonly ButtonLink: { - readonly classNames: { - readonly root: 'bui-ButtonLink'; - }; - }; - readonly Card: { - readonly classNames: { - readonly root: 'bui-Card'; - readonly header: 'bui-CardHeader'; - readonly body: 'bui-CardBody'; - readonly footer: 'bui-CardFooter'; - }; - }; - readonly Checkbox: { - readonly classNames: { - readonly root: 'bui-CheckboxRoot'; - readonly label: 'bui-CheckboxLabel'; - readonly indicator: 'bui-CheckboxIndicator'; - }; - readonly dataAttributes: { - readonly checked: readonly [true, false]; - }; - }; - readonly Collapsible: { - readonly classNames: { - readonly root: 'bui-CollapsibleRoot'; - readonly trigger: 'bui-CollapsibleTrigger'; - readonly panel: 'bui-CollapsiblePanel'; - }; - }; - readonly Container: { - readonly classNames: { - readonly root: 'bui-Container'; - }; - readonly utilityProps: ['my', 'mt', 'mb', 'py', 'pt', 'pb', 'display']; - }; - readonly Dialog: { - readonly classNames: { - readonly overlay: 'bui-DialogOverlay'; - readonly dialog: 'bui-Dialog'; - readonly header: 'bui-DialogHeader'; - readonly headerTitle: 'bui-DialogHeaderTitle'; - readonly body: 'bui-DialogBody'; - readonly footer: 'bui-DialogFooter'; - }; - }; - readonly FieldError: { - readonly classNames: { - readonly root: 'bui-FieldError'; - }; - }; - readonly FieldLabel: { - readonly classNames: { - readonly root: 'bui-FieldLabelWrapper'; - readonly label: 'bui-FieldLabel'; - readonly secondaryLabel: 'bui-FieldSecondaryLabel'; - readonly description: 'bui-FieldDescription'; - }; - }; - readonly Flex: { - readonly classNames: { - readonly root: 'bui-Flex'; - }; - readonly utilityProps: [ - 'm', - 'mb', - 'ml', - 'mr', - 'mt', - 'mx', - 'my', - 'p', - 'pb', - 'pl', - 'pr', - 'pt', - 'px', - 'py', - 'gap', - 'align', - 'justify', - 'direction', - ]; - }; - readonly Grid: { - readonly classNames: { - readonly root: 'bui-Grid'; - }; - readonly utilityProps: [ - 'columns', - 'gap', - 'm', - 'mb', - 'ml', - 'mr', - 'mt', - 'mx', - 'my', - 'p', - 'pb', - 'pl', - 'pr', - 'pt', - 'px', - 'py', - ]; - }; - readonly GridItem: { - readonly classNames: { - readonly root: 'bui-GridItem'; - }; - readonly utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan']; - }; - readonly Header: { - readonly classNames: { - readonly toolbar: 'bui-HeaderToolbar'; - readonly toolbarWrapper: 'bui-HeaderToolbarWrapper'; - readonly toolbarContent: 'bui-HeaderToolbarContent'; - readonly toolbarControls: 'bui-HeaderToolbarControls'; - readonly toolbarIcon: 'bui-HeaderToolbarIcon'; - readonly toolbarName: 'bui-HeaderToolbarName'; - readonly tabsWrapper: 'bui-HeaderTabsWrapper'; - }; - }; - readonly HeaderPage: { - readonly classNames: { - readonly root: 'bui-HeaderPage'; - readonly content: 'bui-HeaderPageContent'; - readonly breadcrumbs: 'bui-HeaderPageBreadcrumbs'; - readonly tabsWrapper: 'bui-HeaderPageTabsWrapper'; - readonly controls: 'bui-HeaderPageControls'; - }; - }; - readonly Heading: { - readonly classNames: { - readonly root: 'bui-Heading'; - }; - readonly dataAttributes: { - readonly variant: readonly ['title1', 'title2', 'title3', 'subtitle']; - readonly color: readonly ['primary', 'secondary', 'muted']; - readonly truncate: readonly [true, false]; - }; - }; - readonly Icon: { - readonly classNames: { - readonly root: 'bui-Icon'; - }; - }; - readonly Link: { - readonly classNames: { - readonly root: 'bui-Link'; - }; - readonly dataAttributes: { - readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; - readonly weight: readonly ['regular', 'bold']; - }; - }; - readonly List: { - readonly classNames: { - readonly root: 'bui-List'; - readonly row: 'bui-ListRow'; - readonly label: 'bui-ListLabel'; - }; - }; - readonly Menu: { - readonly classNames: { - readonly root: 'bui-Menu'; - readonly popover: 'bui-MenuPopover'; - readonly content: 'bui-MenuContent'; - readonly section: 'bui-MenuSection'; - readonly sectionHeader: 'bui-MenuSectionHeader'; - readonly item: 'bui-MenuItem'; - readonly itemListBox: 'bui-MenuItemListBox'; - readonly itemListBoxCheck: 'bui-MenuItemListBoxCheck'; - readonly itemWrapper: 'bui-MenuItemWrapper'; - readonly itemContent: 'bui-MenuItemContent'; - readonly itemArrow: 'bui-MenuItemArrow'; - readonly separator: 'bui-MenuSeparator'; - readonly searchField: 'bui-MenuSearchField'; - readonly searchFieldInput: 'bui-MenuSearchFieldInput'; - readonly searchFieldClear: 'bui-MenuSearchFieldClear'; - readonly emptyState: 'bui-MenuEmptyState'; - }; - }; - readonly PasswordField: { - readonly classNames: { - readonly root: 'bui-PasswordField'; - readonly inputVisibility: 'bui-InputVisibility'; - }; - readonly dataAttributes: { - readonly size: readonly ['small', 'medium']; - }; - }; - readonly Popover: { - readonly classNames: { - readonly root: 'bui-Popover'; - }; - }; - readonly RadioGroup: { - readonly classNames: { - readonly root: 'bui-RadioGroup'; - readonly content: 'bui-RadioGroupContent'; - readonly radio: 'bui-Radio'; - }; - }; - readonly SearchField: { - readonly classNames: { - readonly root: 'bui-SearchField'; - readonly clear: 'bui-InputClear'; - }; - readonly dataAttributes: { - readonly startCollapsed: readonly [true, false]; - readonly size: readonly ['small', 'medium']; - }; - }; - readonly Select: { - readonly classNames: { - readonly root: 'bui-Select'; - readonly trigger: 'bui-SelectTrigger'; - readonly value: 'bui-SelectValue'; - readonly icon: 'bui-SelectIcon'; - readonly list: 'bui-SelectList'; - readonly item: 'bui-SelectItem'; - readonly itemIndicator: 'bui-SelectItemIndicator'; - readonly itemLabel: 'bui-SelectItemLabel'; - }; - readonly dataAttributes: { - readonly size: readonly ['small', 'medium']; - }; - }; - readonly Skeleton: { - readonly classNames: { - readonly root: 'bui-Skeleton'; - }; - }; - readonly Switch: { - readonly classNames: { - readonly root: 'bui-Switch'; - readonly indicator: 'bui-SwitchIndicator'; - }; - }; - readonly Table: { - readonly classNames: { - readonly table: 'bui-Table'; - readonly header: 'bui-TableHeader'; - readonly body: 'bui-TableBody'; - readonly row: 'bui-TableRow'; - readonly head: 'bui-TableHead'; - readonly headContent: 'bui-TableHeadContent'; - readonly headSortButton: 'bui-TableHeadSortButton'; - readonly caption: 'bui-TableCaption'; - readonly cell: 'bui-TableCell'; - readonly cellContentWrapper: 'bui-TableCellContentWrapper'; - readonly cellContent: 'bui-TableCellContent'; - readonly cellIcon: 'bui-TableCellIcon'; - readonly cellProfileAvatar: 'bui-TableCellProfileAvatar'; - readonly cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage'; - readonly cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback'; - readonly cellProfileName: 'bui-TableCellProfileName'; - readonly cellProfileLink: 'bui-TableCellProfileLink'; - }; - }; - readonly TablePagination: { - readonly classNames: { - readonly root: 'bui-TablePagination'; - readonly left: 'bui-TablePaginationLeft'; - readonly right: 'bui-TablePaginationRight'; - readonly select: 'bui-TablePaginationSelect'; - }; - }; - readonly Tabs: { - readonly classNames: { - readonly tabs: 'bui-Tabs'; - readonly tabList: 'bui-TabList'; - readonly tabListWrapper: 'bui-TabListWrapper'; - readonly tab: 'bui-Tab'; - readonly tabActive: 'bui-TabActive'; - readonly tabHovered: 'bui-TabHovered'; - readonly panel: 'bui-TabPanel'; - }; - }; - readonly TagGroup: { - readonly classNames: { - readonly group: 'bui-TagGroup'; - readonly list: 'bui-TagList'; - readonly tag: 'bui-Tag'; - readonly tagIcon: 'bui-TagIcon'; - readonly tagRemoveButton: 'bui-TagRemoveButton'; - }; - }; - readonly Text: { - readonly classNames: { - readonly root: 'bui-Text'; - }; - readonly dataAttributes: { - readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; - readonly weight: readonly ['regular', 'bold']; - readonly color: readonly [ - 'primary', - 'secondary', - 'danger', - 'warning', - 'success', - ]; - readonly truncate: readonly [true, false]; - }; - }; - readonly TextField: { - readonly classNames: { - readonly root: 'bui-TextField'; - readonly inputWrapper: 'bui-InputWrapper'; - readonly input: 'bui-Input'; - readonly inputIcon: 'bui-InputIcon'; - readonly inputAction: 'bui-InputAction'; - }; - readonly dataAttributes: { - readonly invalid: readonly [true, false]; - readonly disabled: readonly [true, false]; - readonly size: readonly ['small', 'medium']; - }; - }; - readonly Tooltip: { - readonly classNames: { - readonly tooltip: 'bui-Tooltip'; - readonly arrow: 'bui-TooltipArrow'; - }; - }; -}; - // @public (undocumented) export const Container: ForwardRefExoticComponent< ContainerProps & RefAttributes >; +// @public +export const ContainerDefinition: { + readonly classNames: { + readonly root: 'bui-Container'; + }; + readonly utilityProps: ['my', 'mt', 'mb', 'py', 'pt', 'pb', 'display']; +}; + // @public (undocumented) export interface ContainerProps { // (undocumented) @@ -796,6 +547,18 @@ export interface DialogBodyProps { className?: string; } +// @public +export const DialogDefinition: { + readonly classNames: { + readonly overlay: 'bui-DialogOverlay'; + readonly dialog: 'bui-Dialog'; + readonly header: 'bui-DialogHeader'; + readonly headerTitle: 'bui-DialogHeaderTitle'; + readonly body: 'bui-DialogBody'; + readonly footer: 'bui-DialogFooter'; + }; +}; + // @public (undocumented) export const DialogFooter: ForwardRefExoticComponent< Omit< @@ -844,8 +607,19 @@ export const FieldLabel: ForwardRefExoticComponent< FieldLabelProps & RefAttributes >; +// @public +export const FieldLabelDefinition: { + readonly classNames: { + readonly root: 'bui-FieldLabelWrapper'; + readonly label: 'bui-FieldLabel'; + readonly secondaryLabel: 'bui-FieldSecondaryLabel'; + readonly description: 'bui-FieldDescription'; + }; +}; + // @public (undocumented) -export interface FieldLabelProps { +export interface FieldLabelProps + extends Pick, 'className'> { description?: string | null; htmlFor?: string; id?: string; @@ -858,6 +632,33 @@ export const Flex: ForwardRefExoticComponent< FlexProps & RefAttributes >; +// @public +export const FlexDefinition: { + readonly classNames: { + readonly root: 'bui-Flex'; + }; + readonly utilityProps: [ + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + 'gap', + 'align', + 'justify', + 'direction', + ]; +}; + // @public (undocumented) export type FlexDirection = 'row' | 'column'; @@ -890,6 +691,39 @@ export const Grid: { >; }; +// @public +export const GridDefinition: { + readonly classNames: { + readonly root: 'bui-Grid'; + }; + readonly utilityProps: [ + 'columns', + 'gap', + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + ]; +}; + +// @public +export const GridItemDefinition: { + readonly classNames: { + readonly root: 'bui-GridItem'; + }; + readonly utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan']; +}; + // @public (undocumented) export interface GridItemProps { // (undocumented) @@ -925,6 +759,19 @@ export interface GridProps extends SpaceProps { // @public export const Header: (props: HeaderProps) => JSX_2.Element; +// @public +export const HeaderDefinition: { + readonly classNames: { + readonly toolbar: 'bui-HeaderToolbar'; + readonly toolbarWrapper: 'bui-HeaderToolbarWrapper'; + readonly toolbarContent: 'bui-HeaderToolbarContent'; + readonly toolbarControls: 'bui-HeaderToolbarControls'; + readonly toolbarIcon: 'bui-HeaderToolbarIcon'; + readonly toolbarName: 'bui-HeaderToolbarName'; + readonly tabsWrapper: 'bui-HeaderTabsWrapper'; + }; +}; + // @public export const HeaderPage: (props: HeaderPageProps) => JSX_2.Element; @@ -936,11 +783,24 @@ export interface HeaderPageBreadcrumb { label: string; } +// @public +export const HeaderPageDefinition: { + readonly classNames: { + readonly root: 'bui-HeaderPage'; + readonly content: 'bui-HeaderPageContent'; + readonly breadcrumbs: 'bui-HeaderPageBreadcrumbs'; + readonly tabsWrapper: 'bui-HeaderPageTabsWrapper'; + readonly controls: 'bui-HeaderPageControls'; + }; +}; + // @public export interface HeaderPageProps { // (undocumented) breadcrumbs?: HeaderPageBreadcrumb[]; // (undocumented) + className?: string; + // (undocumented) customActions?: React.ReactNode; // (undocumented) tabs?: HeaderTab[]; @@ -950,6 +810,8 @@ export interface HeaderPageProps { // @public export interface HeaderProps { + // (undocumented) + className?: string; // (undocumented) customActions?: React.ReactNode; // (undocumented) @@ -989,14 +851,37 @@ export const Link: ForwardRefExoticComponent< LinkProps & RefAttributes >; +// @public +export const LinkDefinition: { + readonly classNames: { + readonly root: 'bui-Link'; + }; + readonly dataAttributes: { + readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; + readonly weight: readonly ['regular', 'bold']; + readonly color: readonly [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'success', + ]; + readonly truncate: readonly [true, false]; + }; +}; + // @public (undocumented) export interface LinkProps extends LinkProps_2 { + // (undocumented) + children?: ReactNode; // (undocumented) color?: | TextColors | TextColorStatus | Partial>; // (undocumented) + title?: string; + // (undocumented) truncate?: boolean; // (undocumented) variant?: TextVariants | Partial>; @@ -1049,6 +934,28 @@ export interface MenuAutocompleteProps virtualized?: boolean; } +// @public +export const MenuDefinition: { + readonly classNames: { + readonly root: 'bui-Menu'; + readonly popover: 'bui-MenuPopover'; + readonly content: 'bui-MenuContent'; + readonly section: 'bui-MenuSection'; + readonly sectionHeader: 'bui-MenuSectionHeader'; + readonly item: 'bui-MenuItem'; + readonly itemListBox: 'bui-MenuItemListBox'; + readonly itemListBoxCheck: 'bui-MenuItemListBoxCheck'; + readonly itemWrapper: 'bui-MenuItemWrapper'; + readonly itemContent: 'bui-MenuItemContent'; + readonly itemArrow: 'bui-MenuItemArrow'; + readonly separator: 'bui-MenuSeparator'; + readonly searchField: 'bui-MenuSearchField'; + readonly searchFieldInput: 'bui-MenuSearchFieldInput'; + readonly searchFieldClear: 'bui-MenuSearchFieldClear'; + readonly emptyState: 'bui-MenuEmptyState'; + }; +}; + // @public (undocumented) export const MenuItem: (props: MenuItemProps) => JSX_2.Element; @@ -1131,6 +1038,14 @@ export const MenuTrigger: (props: MenuTriggerProps) => JSX_2.Element; // @public (undocumented) export interface MenuTriggerProps extends MenuTriggerProps_2 {} +// @public (undocumented) +type Option_2 = { + value: string; + label: string; + disabled?: boolean; +}; +export { Option_2 as Option }; + // @public (undocumented) export const Radio: ForwardRefExoticComponent< RadioProps & RefAttributes @@ -1141,10 +1056,19 @@ export const RadioGroup: ForwardRefExoticComponent< RadioGroupProps & RefAttributes >; +// @public +export const RadioGroupDefinition: { + readonly classNames: { + readonly root: 'bui-RadioGroup'; + readonly content: 'bui-RadioGroupContent'; + readonly radio: 'bui-Radio'; + }; +}; + // @public (undocumented) export interface RadioGroupProps extends Omit, - Omit { + Omit { // (undocumented) children?: ReactNode; } @@ -1163,10 +1087,25 @@ export const SearchField: ForwardRefExoticComponent< SearchFieldProps & RefAttributes >; +// @public +export const SearchFieldDefinition: { + readonly classNames: { + readonly root: 'bui-SearchField'; + readonly clear: 'bui-SearchFieldClear'; + readonly inputWrapper: 'bui-SearchFieldInputWrapper'; + readonly input: 'bui-SearchFieldInput'; + readonly inputIcon: 'bui-SearchFieldInputIcon'; + }; + readonly dataAttributes: { + readonly startCollapsed: readonly [true, false]; + readonly size: readonly ['small', 'medium']; + }; +}; + // @public (undocumented) export interface SearchFieldProps extends SearchFieldProps_2, - Omit { + Omit { icon?: ReactNode | false; placeholder?: string; size?: 'small' | 'medium' | Partial>; @@ -1175,28 +1114,53 @@ export interface SearchFieldProps // @public (undocumented) export const Select: ForwardRefExoticComponent< - SelectProps & RefAttributes + SelectProps<'multiple' | 'single'> & RefAttributes >; +// @public +export const SelectDefinition: { + readonly classNames: { + readonly root: 'bui-Select'; + readonly popover: 'bui-SelectPopover'; + readonly trigger: 'bui-SelectTrigger'; + readonly chevron: 'bui-SelectTriggerChevron'; + readonly value: 'bui-SelectValue'; + readonly list: 'bui-SelectList'; + readonly item: 'bui-SelectItem'; + readonly itemIndicator: 'bui-SelectItemIndicator'; + readonly itemLabel: 'bui-SelectItemLabel'; + readonly searchWrapper: 'bui-SelectSearchWrapper'; + readonly search: 'bui-SelectSearch'; + readonly searchClear: 'bui-SelectSearchClear'; + readonly noResults: 'bui-SelectNoResults'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium']; + }; +}; + // @public (undocumented) -export interface SelectProps - extends SelectProps_2<{ - name: string; - value: string; - }>, - Omit { +export interface SelectProps + extends SelectProps_2, + Omit { icon?: ReactNode; - options?: Array<{ - value: string; - label: string; - disabled?: boolean; - }>; + options?: Array; + searchable?: boolean; + searchPlaceholder?: string; + selectionMode?: T; size?: 'small' | 'medium' | Partial>; } // @public (undocumented) export const Skeleton: (props: SkeletonProps) => JSX_2.Element; +// @public +export const SkeletonDefinition: { + readonly classNames: { + readonly root: 'bui-Skeleton'; + }; +}; + // @public (undocumented) export interface SkeletonProps extends ComponentProps<'div'> { // (undocumented) @@ -1270,6 +1234,14 @@ export const Switch: ForwardRefExoticComponent< SwitchProps & RefAttributes >; +// @public +export const SwitchDefinition: { + readonly classNames: { + readonly root: 'bui-Switch'; + readonly indicator: 'bui-SwitchIndicator'; + }; +}; + // @public (undocumented) export interface SwitchProps extends SwitchProps_2 { label?: string; @@ -1286,6 +1258,31 @@ export const TableBody: ( props: TableBodyProps, ) => JSX_2.Element; +// @public +export const TableDefinition: { + readonly classNames: { + readonly table: 'bui-Table'; + readonly header: 'bui-TableHeader'; + readonly body: 'bui-TableBody'; + readonly row: 'bui-TableRow'; + readonly head: 'bui-TableHead'; + readonly headContent: 'bui-TableHeadContent'; + readonly headSortButton: 'bui-TableHeadSortButton'; + readonly caption: 'bui-TableCaption'; + readonly cell: 'bui-TableCell'; + readonly cellContentWrapper: 'bui-TableCellContentWrapper'; + readonly cellContent: 'bui-TableCellContent'; + readonly cellIcon: 'bui-TableCellIcon'; + readonly cellProfileAvatar: 'bui-TableCellProfileAvatar'; + readonly cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage'; + readonly cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback'; + readonly cellProfileName: 'bui-TableCellProfileName'; + readonly cellProfileLink: 'bui-TableCellProfileLink'; + readonly headSelection: 'bui-TableHeadSelection'; + readonly cellSelection: 'bui-TableCellSelection'; + }; +}; + // @public (undocumented) export const TableHeader: ( props: TableHeaderProps, @@ -1294,6 +1291,16 @@ export const TableHeader: ( // @public export function TablePagination(props: TablePaginationProps): JSX_2.Element; +// @public +export const TablePaginationDefinition: { + readonly classNames: { + readonly root: 'bui-TablePagination'; + readonly left: 'bui-TablePaginationLeft'; + readonly right: 'bui-TablePaginationRight'; + readonly select: 'bui-TablePaginationSelect'; + }; +}; + // @public (undocumented) export interface TablePaginationProps extends React.HTMLAttributes { @@ -1340,6 +1347,19 @@ export interface TabProps extends TabProps_2 { // @public export const Tabs: (props: TabsProps) => JSX_2.Element | null; +// @public +export const TabsDefinition: { + readonly classNames: { + readonly tabs: 'bui-Tabs'; + readonly tabList: 'bui-TabList'; + readonly tabListWrapper: 'bui-TabListWrapper'; + readonly tab: 'bui-Tab'; + readonly tabActive: 'bui-TabActive'; + readonly tabHovered: 'bui-TabHovered'; + readonly panel: 'bui-TabPanel'; + }; +}; + // @public export interface TabsProps extends TabsProps_2 {} @@ -1351,6 +1371,17 @@ export const TagGroup: ( props: TagGroupProps, ) => JSX_2.Element; +// @public +export const TagGroupDefinition: { + readonly classNames: { + readonly group: 'bui-TagGroup'; + readonly list: 'bui-TagList'; + readonly tag: 'bui-Tag'; + readonly tagIcon: 'bui-TagIcon'; + readonly tagRemoveButton: 'bui-TagRemoveButton'; + }; +}; + // @public export interface TagGroupProps extends Omit, @@ -1379,15 +1410,50 @@ export type TextColors = 'primary' | 'secondary'; // @public (undocumented) export type TextColorStatus = 'danger' | 'warning' | 'success'; +// @public +export const TextDefinition: { + readonly classNames: { + readonly root: 'bui-Text'; + }; + readonly dataAttributes: { + readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; + readonly weight: readonly ['regular', 'bold']; + readonly color: readonly [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'success', + ]; + readonly truncate: readonly [true, false]; + }; +}; + // @public (undocumented) export const TextField: ForwardRefExoticComponent< TextFieldProps & RefAttributes >; +// @public +export const TextFieldDefinition: { + readonly classNames: { + readonly root: 'bui-TextField'; + readonly inputWrapper: 'bui-InputWrapper'; + readonly input: 'bui-Input'; + readonly inputIcon: 'bui-InputIcon'; + readonly inputAction: 'bui-InputAction'; + }; + readonly dataAttributes: { + readonly invalid: readonly [true, false]; + readonly disabled: readonly [true, false]; + readonly size: readonly ['small', 'medium']; + }; +}; + // @public (undocumented) export interface TextFieldProps extends TextFieldProps_2, - Omit { + Omit { icon?: ReactNode; placeholder?: string; size?: 'small' | 'medium' | Partial>; @@ -1443,6 +1509,14 @@ export const Tooltip: ForwardRefExoticComponent< TooltipProps & RefAttributes >; +// @public +export const TooltipDefinition: { + readonly classNames: { + readonly tooltip: 'bui-Tooltip'; + readonly arrow: 'bui-TooltipArrow'; + }; +}; + // @public (undocumented) export interface TooltipProps extends Omit { // (undocumented) @@ -1534,4 +1608,20 @@ export interface UtilityProps extends SpaceProps { // (undocumented) rowSpan?: Responsive; } + +// @public +export const VisuallyHidden: (props: VisuallyHiddenProps) => JSX_2.Element; + +// @public +export const VisuallyHiddenDefinition: { + readonly classNames: { + readonly root: 'bui-VisuallyHidden'; + }; +}; + +// @public +export interface VisuallyHiddenProps extends ComponentProps<'div'> { + // (undocumented) + children?: React.ReactNode; +} ``` diff --git a/packages/ui/src/components/Accordion/Accordion.module.css b/packages/ui/src/components/Accordion/Accordion.module.css new file mode 100644 index 0000000000..a86400986d --- /dev/null +++ b/packages/ui/src/components/Accordion/Accordion.module.css @@ -0,0 +1,93 @@ +/* + * 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. + */ + +@layer tokens, base, components, utilities; + +@layer components { + .bui-Accordion { + width: 100%; + background-color: var(--bui-bg-surface-1); + border-radius: var(--bui-radius-3); + padding: var(--bui-space-3); + } + + .bui-AccordionTrigger { + all: unset; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + } + + .bui-AccordionTriggerButton { + all: unset; + width: 100%; + color: var(--bui-fg-primary); + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + text-align: left; + + &:focus-visible { + outline: none; + transition: none; + box-shadow: inset 0 0 0 2px var(--bui-ring); + } + + &[data-disabled='true'] { + background-color: transparent; + color: var(--bui-fg-disabled); + cursor: not-allowed; + } + } + + .bui-AccordionTriggerTitle { + font-size: var(--bui-font-size-4); + font-weight: var(--bui-font-weight-bold); + line-height: 140%; + } + + .bui-AccordionTriggerSubtitle { + font-size: var(--bui-font-size-2); + line-height: 140%; + color: var(--bui-fg-secondary); + } + + .bui-AccordionTriggerIcon { + transition: transform 150ms ease-out; + flex-shrink: 0; + width: 1rem; + height: 1rem; + + [data-expanded='true'] & { + transform: rotate(180deg); + } + } + + .bui-AccordionPanel { + [data-expanded='true'] & { + padding-top: var(--bui-space-1); + } + } + + .bui-AccordionGroup { + display: flex; + flex-direction: column; + gap: var(--bui-space-3); + width: 100%; + } +} diff --git a/packages/ui/src/components/Accordion/Accordion.stories.tsx b/packages/ui/src/components/Accordion/Accordion.stories.tsx new file mode 100644 index 0000000000..6640bd5d6a --- /dev/null +++ b/packages/ui/src/components/Accordion/Accordion.stories.tsx @@ -0,0 +1,177 @@ +/* + * 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 type { Meta, StoryObj } from '@storybook/react-vite'; +import { + Accordion, + AccordionTrigger, + AccordionPanel, + AccordionGroup, +} from './Accordion'; +import { Box } from '../Box'; +import { Text } from '../Text'; + +const Content = () => ( + + + It's the edge of the world and all of Western civilization + + + The sun may rise in the East, at least it settled in a final location + + It's understood that Hollywood sells Californication + +); + +const meta = { + title: 'Backstage UI/Accordion', + component: Accordion, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + + + + + + ), +}; + +export const WithSubtitle: Story = { + render: () => ( + + + + + + + ), +}; + +export const CustomTrigger: Story = { + render: () => ( + + + + + Custom Multi-line Trigger + + + Click to expand additional details and configuration options + + + + + + + + ), +}; + +export const DefaultExpanded: Story = { + render: () => ( + + + + + + + ), +}; + +export const GroupSingleOpen: Story = { + render: () => ( + + + + + + + It's the edge of the world and all of Western civilization + + + + + + + + + + The sun may rise in the East, at least it settled in a final + location + + + + + + + + + + It's understood that Hollywood sells Californication + + + + + + ), +}; + +export const GroupMultipleOpen: Story = { + render: () => ( + + + + + + + It's the edge of the world and all of Western civilization + + + + + + + + + + The sun may rise in the East, at least it settled in a final + location + + + + + + + + + + It's understood that Hollywood sells Californication + + + + + + ), +}; diff --git a/packages/ui/src/components/Accordion/Accordion.tsx b/packages/ui/src/components/Accordion/Accordion.tsx new file mode 100644 index 0000000000..f31b923490 --- /dev/null +++ b/packages/ui/src/components/Accordion/Accordion.tsx @@ -0,0 +1,154 @@ +/* + * 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 { forwardRef } from 'react'; +import { + Disclosure as RADisclosure, + Button as RAButton, + DisclosurePanel as RADisclosurePanel, + DisclosureGroup as RADisclosureGroup, + Heading as RAHeading, +} from 'react-aria-components'; +import { RiArrowDownSLine } from '@remixicon/react'; +import clsx from 'clsx'; +import type { + AccordionProps, + AccordionTriggerProps, + AccordionPanelProps, + AccordionGroupProps, +} from './types'; +import { useStyles } from '../../hooks/useStyles'; +import { AccordionDefinition } from './definition'; +import styles from './Accordion.module.css'; +import { Flex } from '../Flex'; + +/** @public */ +export const Accordion = forwardRef< + React.ElementRef, + AccordionProps +>(({ className, ...props }, ref) => { + const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); + + return ( + + ); +}); + +Accordion.displayName = 'Accordion'; + +/** @public */ +export const AccordionTrigger = forwardRef< + React.ElementRef, + AccordionTriggerProps +>(({ className, title, subtitle, children, ...props }, ref) => { + const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); + + return ( + + + {children ? ( + children + ) : ( + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + )} + + + + + ); +}); + +AccordionTrigger.displayName = 'AccordionTrigger'; + +/** @public */ +export const AccordionPanel = forwardRef< + React.ElementRef, + AccordionPanelProps +>(({ className, ...props }, ref) => { + const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); + + return ( + + ); +}); + +AccordionPanel.displayName = 'AccordionPanel'; + +/** @public */ +export const AccordionGroup = forwardRef< + React.ElementRef, + AccordionGroupProps +>(({ className, allowsMultiple = false, ...props }, ref) => { + const { classNames, cleanedProps } = useStyles(AccordionDefinition, props); + + return ( + + ); +}); + +AccordionGroup.displayName = 'AccordionGroup'; diff --git a/packages/ui/src/components/Accordion/definition.ts b/packages/ui/src/components/Accordion/definition.ts new file mode 100644 index 0000000000..afc508b922 --- /dev/null +++ b/packages/ui/src/components/Accordion/definition.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Accordion + * @public + */ +export const AccordionDefinition = { + classNames: { + root: 'bui-Accordion', + trigger: 'bui-AccordionTrigger', + triggerButton: 'bui-AccordionTriggerButton', + triggerTitle: 'bui-AccordionTriggerTitle', + triggerSubtitle: 'bui-AccordionTriggerSubtitle', + triggerIcon: 'bui-AccordionTriggerIcon', + panel: 'bui-AccordionPanel', + group: 'bui-AccordionGroup', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Accordion/index.ts b/packages/ui/src/components/Accordion/index.ts new file mode 100644 index 0000000000..e20f49bec7 --- /dev/null +++ b/packages/ui/src/components/Accordion/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { + Accordion, + AccordionTrigger, + AccordionPanel, + AccordionGroup, +} from './Accordion'; +export { AccordionDefinition } from './definition'; +export type { + AccordionProps, + AccordionTriggerProps, + AccordionPanelProps, + AccordionGroupProps, +} from './types'; diff --git a/packages/ui/src/components/Accordion/types.ts b/packages/ui/src/components/Accordion/types.ts new file mode 100644 index 0000000000..06c8549ebd --- /dev/null +++ b/packages/ui/src/components/Accordion/types.ts @@ -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 type { + DisclosureProps as RADisclosureProps, + HeadingProps as RAHeadingProps, + DisclosurePanelProps as RADisclosurePanelProps, + DisclosureGroupProps as RADisclosureGroupProps, +} from 'react-aria-components'; + +/** + * Props for the Accordion component. + * @public + */ +export interface AccordionProps extends RADisclosureProps { + className?: string; +} + +/** + * Props for the AccordionTrigger component. + * @public + */ +export interface AccordionTriggerProps extends RAHeadingProps { + className?: string; + title?: string; + subtitle?: string; + children?: React.ReactNode; +} + +/** + * Props for the AccordionPanel component. + * @public + */ +export interface AccordionPanelProps extends RADisclosurePanelProps { + className?: string; +} + +/** + * Props for the AccordionGroup component. + * @public + */ +export interface AccordionGroupProps extends RADisclosureGroupProps { + className?: string; + /** + * Whether multiple accordions can be expanded at the same time. + * @defaultValue false + */ + allowsMultiple?: boolean; +} diff --git a/packages/ui/src/components/Avatar/Avatar.module.css b/packages/ui/src/components/Avatar/Avatar.module.css index f1517db3c2..c1fec3df9b 100644 --- a/packages/ui/src/components/Avatar/Avatar.module.css +++ b/packages/ui/src/components/Avatar/Avatar.module.css @@ -34,6 +34,11 @@ width: 2rem; } + .bui-AvatarRoot[data-size='x-small'] { + height: 1.25rem; + width: 1.25rem; + } + .bui-AvatarRoot[data-size='small'] { height: 1.5rem; width: 1.5rem; @@ -45,6 +50,11 @@ } .bui-AvatarRoot[data-size='large'] { + height: 2.5rem; + width: 2.5rem; + } + + .bui-AvatarRoot[data-size='x-large'] { height: 3rem; width: 3rem; } @@ -53,6 +63,7 @@ object-fit: cover; height: 100%; width: 100%; + display: block; } .bui-AvatarFallback { diff --git a/packages/ui/src/components/Avatar/Avatar.stories.tsx b/packages/ui/src/components/Avatar/Avatar.stories.tsx index c91fe736db..582d82393e 100644 --- a/packages/ui/src/components/Avatar/Avatar.stories.tsx +++ b/packages/ui/src/components/Avatar/Avatar.stories.tsx @@ -16,7 +16,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { Avatar } from './index'; -import { Flex } from '../..'; +import { Flex, Text } from '../..'; const meta = { title: 'Backstage UI/Avatar', @@ -45,10 +45,52 @@ export const Sizes: Story = { ...Default.args, }, render: args => ( - - - - + + + + + + + + + + + + + + + + + ), +}; + +export const Purpose: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + Informative (default) + + Use when avatar appears alone. Announced as "{args.name}" to screen + readers: + + + + + + + Decoration + + Use when name appears adjacent to avatar. Hidden from screen readers + to avoid redundancy: + + + + {args.name} + + ), }; diff --git a/packages/ui/src/components/Avatar/Avatar.tsx b/packages/ui/src/components/Avatar/Avatar.tsx index 000087d451..920a9b127f 100644 --- a/packages/ui/src/components/Avatar/Avatar.tsx +++ b/packages/ui/src/components/Avatar/Avatar.tsx @@ -14,48 +14,80 @@ * limitations under the License. */ -import { forwardRef, ElementRef } from 'react'; -import { Avatar as AvatarPrimitive } from '@base-ui-components/react/avatar'; +import { forwardRef, useState, useEffect } from 'react'; import clsx from 'clsx'; import { AvatarProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { AvatarDefinition } from './definition'; import styles from './Avatar.module.css'; /** @public */ -export const Avatar = forwardRef< - ElementRef, - AvatarProps ->((props, ref) => { - const { classNames, dataAttributes, cleanedProps } = useStyles('Avatar', { - size: 'medium', - ...props, - }); +export const Avatar = forwardRef((props, ref) => { + const { classNames, dataAttributes, cleanedProps } = useStyles( + AvatarDefinition, + { + size: 'medium', + purpose: 'informative', + ...props, + }, + ); - const { className, src, name, ...rest } = cleanedProps; + const { className, src, name, purpose, ...rest } = cleanedProps; + + const [imageStatus, setImageStatus] = useState< + 'loading' | 'loaded' | 'error' + >('loading'); + + useEffect(() => { + setImageStatus('loading'); + const img = new Image(); + img.onload = () => setImageStatus('loaded'); + img.onerror = () => setImageStatus('error'); + img.src = src; + + return () => { + img.onload = null; + img.onerror = null; + }; + }, [src]); + + const initialsCount = ['x-small', 'small'].includes(cleanedProps.size) + ? 1 + : 2; + + const initials = name + .split(' ') + .map(word => word[0]) + .join('') + .toLocaleUpperCase('en-US') + .slice(0, initialsCount); return ( - - - - {(name || '') - .split(' ') - .map(word => word[0]) - .join('') - .toLocaleUpperCase('en-US') - .slice(0, 2)} - - + {imageStatus === 'loaded' ? ( + + ) : ( + + )} +
  • ); }); -Avatar.displayName = AvatarPrimitive.Root.displayName; +Avatar.displayName = 'Avatar'; diff --git a/packages/ui/src/components/Avatar/definition.ts b/packages/ui/src/components/Avatar/definition.ts new file mode 100644 index 0000000000..8ad2c31bdc --- /dev/null +++ b/packages/ui/src/components/Avatar/definition.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Avatar + * @public + */ +export const AvatarDefinition = { + classNames: { + root: 'bui-AvatarRoot', + image: 'bui-AvatarImage', + fallback: 'bui-AvatarFallback', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Avatar/index.ts b/packages/ui/src/components/Avatar/index.ts index d5ab42beb2..38e896bdb4 100644 --- a/packages/ui/src/components/Avatar/index.ts +++ b/packages/ui/src/components/Avatar/index.ts @@ -15,4 +15,5 @@ */ export { Avatar } from './Avatar'; +export { AvatarDefinition } from './definition'; export type { AvatarProps } from './types'; diff --git a/packages/ui/src/components/Avatar/types.ts b/packages/ui/src/components/Avatar/types.ts index b2e6cf39cf..a252861ea4 100644 --- a/packages/ui/src/components/Avatar/types.ts +++ b/packages/ui/src/components/Avatar/types.ts @@ -14,12 +14,29 @@ * limitations under the License. */ -import { Avatar } from '@base-ui-components/react/avatar'; - /** @public */ -export interface AvatarProps - extends React.ComponentPropsWithoutRef { +export interface AvatarProps extends React.ComponentPropsWithoutRef<'div'> { + /** + * URL of the image to display + */ src: string; + + /** + * Name of the person - used for generating initials and accessibility labels + */ name: string; - size?: 'small' | 'medium' | 'large'; + + /** + * Size of the avatar + * @defaultValue 'medium' + */ + size?: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; + + /** + * Determines how the avatar is presented to assistive technologies. + * - 'informative': Avatar is announced as "\{name\}" to screen readers + * - 'decoration': Avatar is hidden from screen readers (use when name appears in adjacent text) + * @defaultValue 'informative' + */ + purpose?: 'decoration' | 'informative'; } diff --git a/packages/ui/src/components/Box/Box.tsx b/packages/ui/src/components/Box/Box.tsx index fdcee3d382..34c337824b 100644 --- a/packages/ui/src/components/Box/Box.tsx +++ b/packages/ui/src/components/Box/Box.tsx @@ -19,11 +19,12 @@ import { BoxProps } from './types'; import clsx from 'clsx'; import { useStyles } from '../../hooks/useStyles'; import styles from './Box.module.css'; +import { BoxDefinition } from './definition'; /** @public */ export const Box = forwardRef((props, ref) => { const { classNames, utilityClasses, style, cleanedProps } = useStyles( - 'Box', + BoxDefinition, props, ); diff --git a/packages/ui/src/components/Box/definition.ts b/packages/ui/src/components/Box/definition.ts new file mode 100644 index 0000000000..f5b6fc6553 --- /dev/null +++ b/packages/ui/src/components/Box/definition.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Box + * @public + */ +export const BoxDefinition = { + classNames: { + root: 'bui-Box', + }, + utilityProps: [ + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + 'position', + 'display', + 'width', + 'minWidth', + 'maxWidth', + 'height', + 'minHeight', + 'maxHeight', + ], +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Box/index.tsx b/packages/ui/src/components/Box/index.tsx index d47e653dfd..a2b2861efb 100644 --- a/packages/ui/src/components/Box/index.tsx +++ b/packages/ui/src/components/Box/index.tsx @@ -14,4 +14,5 @@ * limitations under the License. */ export { Box } from './Box'; +export { BoxDefinition } from './definition'; export type * from './types'; diff --git a/packages/ui/src/components/Button/Button.module.css b/packages/ui/src/components/Button/Button.module.css index 2216d0882b..a87f5d67cf 100644 --- a/packages/ui/src/components/Button/Button.module.css +++ b/packages/ui/src/components/Button/Button.module.css @@ -18,22 +18,27 @@ @layer components { .bui-Button { - border: none; + --loading-duration: 200ms; + position: relative; display: inline-flex; - align-items: center; - justify-content: center; + border: none; user-select: none; font-family: var(--bui-font-regular); font-weight: var(--bui-font-weight-bold); padding: 0; cursor: pointer; border-radius: var(--bui-radius-2); - gap: var(--bui-space-1_5); flex-shrink: 0; + transition: background-color var(--loading-duration) ease-out, + box-shadow var(--loading-duration) ease-out; &[data-disabled='true'] { cursor: not-allowed; } + + &[data-loading='true'] { + cursor: wait; + } } .bui-Button[data-variant='primary'] { @@ -54,7 +59,8 @@ outline-offset: 2px; } - &[data-disabled='true'] { + &[data-disabled='true'], + &[data-loading='true'] { background-color: var(--bui-bg-solid-disabled); color: var(--bui-fg-solid-disabled); } @@ -80,7 +86,8 @@ box-shadow: inset 0 0 0 2px var(--bui-ring); } - &[data-disabled='true'] { + &[data-disabled='true'], + &[data-loading='true'] { box-shadow: inset 0 0 0 1px var(--bui-border-disabled); color: var(--bui-fg-disabled); } @@ -105,31 +112,92 @@ box-shadow: inset 0 0 0 2px var(--bui-ring); } - &[data-disabled='true'] { + &[data-disabled='true'], + &[data-loading='true'] { background-color: transparent; color: var(--bui-fg-disabled); } } + .bui-Button[data-size='small'] { + font-size: var(--bui-font-size-3); + padding: 0 var(--bui-space-2); + height: 2rem; + + svg { + width: 1rem; + height: 1rem; + } + } + .bui-Button[data-size='medium'] { font-size: var(--bui-font-size-4); padding: 0 var(--bui-space-3); height: 2.5rem; + + svg { + width: 1.25rem; + height: 1.25rem; + } } - .bui-Button[data-size='small'] { - font-size: var(--bui-font-size-3); - padding: 0 var(--bui-space-2); - height: 2rem; + .bui-ButtonContent { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--bui-space-1_5); + height: 100%; + width: 100%; + transition: opacity var(--loading-duration) ease-out; + + .bui-Button[data-loading='true'] & { + opacity: 0; + } } - .bui-Button[data-size='small'] svg { - width: 1rem; - height: 1rem; + .bui-ButtonSpinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + opacity: 0; + transition: opacity var(--loading-duration) ease-in; + + .bui-Button[data-loading='true'] & { + opacity: 1; + } + + & svg { + animation: bui-spin 1s linear infinite; + } } - .bui-Button[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; + @media (prefers-reduced-motion: reduce) { + .bui-Button { + transition-duration: 50ms; + } + + .bui-ButtonContent { + transition-duration: 50ms; + } + + .bui-ButtonSpinner { + transition-duration: 50ms; + } + + .bui-ButtonSpinner svg { + animation: none; + } + } + + @keyframes bui-spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } } } diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx index 6b74615eeb..4734770aa4 100644 --- a/packages/ui/src/components/Button/Button.stories.tsx +++ b/packages/ui/src/components/Button/Button.stories.tsx @@ -19,6 +19,7 @@ import { Button } from './Button'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; +import { useState } from 'react'; const meta = { title: 'Backstage UI/Button', @@ -212,3 +213,80 @@ export const Playground: Story = { ), }; + +export const Loading: Story = { + render: () => { + const [isLoading, setIsLoading] = useState(false); + + const handleClick = () => { + setIsLoading(true); + setTimeout(() => { + setIsLoading(false); + }, 3000); + }; + + return ( + + ); + }, +}; + +export const LoadingVariants: Story = { + render: () => ( + + Primary + + + + + + + Secondary + + + + + + + Tertiary + + + + + + + Loading vs Disabled + + + + + + + ), +}; diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index d22a3036e6..f010e5e57d 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -16,32 +16,57 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; -import { Button as RAButton } from 'react-aria-components'; +import { Button as RAButton, ProgressBar } from 'react-aria-components'; +import { RiLoader4Line } from '@remixicon/react'; import type { ButtonProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { ButtonDefinition } from './definition'; import styles from './Button.module.css'; /** @public */ export const Button = forwardRef( (props: ButtonProps, ref: Ref) => { - const { classNames, dataAttributes, cleanedProps } = useStyles('Button', { - size: 'small', - variant: 'primary', - ...props, - }); + const { classNames, dataAttributes, cleanedProps } = useStyles( + ButtonDefinition, + { + size: 'small', + variant: 'primary', + ...props, + }, + ); - const { children, className, iconStart, iconEnd, ...rest } = cleanedProps; + const { children, className, iconStart, iconEnd, loading, ...rest } = + cleanedProps; return ( - {iconStart} - {children} - {iconEnd} + {({ isPending }) => ( + <> + + {iconStart} + {children} + {iconEnd} + + + {isPending && ( + + + )} + + )} ); }, diff --git a/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts b/packages/ui/src/components/Button/definition.ts similarity index 53% rename from packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts rename to packages/ui/src/components/Button/definition.ts index 1207e84755..a017157774 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ b/packages/ui/src/components/Button/definition.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,21 @@ * limitations under the License. */ -import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; -import { Observable } from '@backstage/types'; - -/** @alpha */ -export type AppLanguageApi = { - getAvailableLanguages(): { languages: string[] }; - - setLanguage(language?: string): void; - - getLanguage(): { language: string }; - - language$(): Observable<{ language: string }>; -}; +import type { ComponentDefinition } from '../../types'; /** - * @alpha + * Component definition for Button + * @public */ -export const appLanguageApiRef: ApiRef = createApiRef({ - id: 'core.applanguage', -}); +export const ButtonDefinition = { + classNames: { + root: 'bui-Button', + content: 'bui-ButtonContent', + spinner: 'bui-ButtonSpinner', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + variant: ['primary', 'secondary', 'tertiary'] as const, + loading: [true, false] as const, + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Button/index.ts b/packages/ui/src/components/Button/index.ts index b9c497ae1f..e624fcedde 100644 --- a/packages/ui/src/components/Button/index.ts +++ b/packages/ui/src/components/Button/index.ts @@ -16,3 +16,4 @@ export * from './Button'; export * from './types'; +export { ButtonDefinition } from './definition'; diff --git a/packages/ui/src/components/Button/types.ts b/packages/ui/src/components/Button/types.ts index cbe5bd3608..daeba7653f 100644 --- a/packages/ui/src/components/Button/types.ts +++ b/packages/ui/src/components/Button/types.ts @@ -33,4 +33,5 @@ export interface ButtonProps extends RAButtonProps { iconStart?: ReactElement; iconEnd?: ReactElement; children?: ReactNode; + loading?: boolean; } diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx index c1a908119f..97072c3664 100644 --- a/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx @@ -17,7 +17,9 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { ButtonIcon } from './ButtonIcon'; import { Flex } from '../Flex'; +import { Text } from '../Text'; import { RiCloudLine } from '@remixicon/react'; +import { useState } from 'react'; const meta = { title: 'Backstage UI/ButtonIcon', @@ -83,3 +85,91 @@ export const Responsive: Story = { }, render: args => } />, }; + +export const Loading: Story = { + render: () => { + const [isLoading, setIsLoading] = useState(false); + + const handleClick = () => { + setIsLoading(true); + setTimeout(() => { + setIsLoading(false); + }, 3000); + }; + + return ( + } + loading={isLoading} + onPress={handleClick} + /> + ); + }, +}; + +export const LoadingVariants: Story = { + render: () => ( + + Primary + + } + loading + /> + } + loading + /> + + + Secondary + + } + loading + /> + } + loading + /> + + + Tertiary + + } + loading + /> + } + loading + /> + + + Loading vs Disabled + + } loading /> + } isDisabled /> + } + loading + isDisabled + /> + + + ), +}; diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx index 2e6a96c4c1..9bbeca9d5b 100644 --- a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx @@ -16,24 +16,31 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; -import { Button as RAButton } from 'react-aria-components'; +import { Button as RAButton, ProgressBar } from 'react-aria-components'; +import { RiLoader4Line } from '@remixicon/react'; import type { ButtonIconProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { ButtonDefinition } from '../Button/definition'; +import { ButtonIconDefinition } from './definition'; import stylesButtonIcon from './ButtonIcon.module.css'; import stylesButton from '../Button/Button.module.css'; /** @public */ export const ButtonIcon = forwardRef( (props: ButtonIconProps, ref: Ref) => { - const { classNames, dataAttributes, cleanedProps } = useStyles('Button', { - size: 'small', - variant: 'primary', - ...props, - }); + const { classNames, dataAttributes, cleanedProps } = useStyles( + ButtonDefinition, + { + size: 'small', + variant: 'primary', + ...props, + }, + ); - const { classNames: classNamesButtonIcon } = useStyles('ButtonIcon'); + const { classNames: classNamesButtonIcon } = + useStyles(ButtonIconDefinition); - const { className, icon, ...rest } = cleanedProps; + const { className, icon, loading, ...rest } = cleanedProps; return ( - {icon} + {({ isPending }) => ( + <> + + {icon} + + + {isPending && ( + + + )} + + )} ); }, diff --git a/packages/ui/src/components/ButtonIcon/definition.ts b/packages/ui/src/components/ButtonIcon/definition.ts new file mode 100644 index 0000000000..48d0a558ab --- /dev/null +++ b/packages/ui/src/components/ButtonIcon/definition.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for ButtonIcon + * @public + */ +export const ButtonIconDefinition = { + classNames: { + root: 'bui-ButtonIcon', + content: 'bui-ButtonIconContent', + spinner: 'bui-ButtonIconSpinner', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/ButtonIcon/index.tsx b/packages/ui/src/components/ButtonIcon/index.tsx index 686fd844e8..acede912ad 100644 --- a/packages/ui/src/components/ButtonIcon/index.tsx +++ b/packages/ui/src/components/ButtonIcon/index.tsx @@ -15,4 +15,5 @@ */ export * from './ButtonIcon'; +export { ButtonIconDefinition } from './definition'; export * from './types'; diff --git a/packages/ui/src/components/ButtonIcon/types.ts b/packages/ui/src/components/ButtonIcon/types.ts index bc358b6d7d..6af3d12361 100644 --- a/packages/ui/src/components/ButtonIcon/types.ts +++ b/packages/ui/src/components/ButtonIcon/types.ts @@ -31,4 +31,5 @@ export interface ButtonIconProps extends RAButtonProps { | 'tertiary' | Partial>; icon?: ReactElement; + loading?: boolean; } diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.tsx b/packages/ui/src/components/ButtonLink/ButtonLink.tsx index 70d216b3d8..db3c95ee54 100644 --- a/packages/ui/src/components/ButtonLink/ButtonLink.tsx +++ b/packages/ui/src/components/ButtonLink/ButtonLink.tsx @@ -20,6 +20,8 @@ import { Link as RALink, RouterProvider } from 'react-aria-components'; import { useNavigate, useHref } from 'react-router-dom'; import type { ButtonLinkProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { ButtonDefinition } from '../Button/definition'; +import { ButtonLinkDefinition } from './definition'; import { isExternalLink } from '../../utils/isExternalLink'; import stylesButton from '../Button/Button.module.css'; @@ -28,60 +30,55 @@ export const ButtonLink = forwardRef( (props: ButtonLinkProps, ref: Ref) => { const navigate = useNavigate(); - const { classNames, dataAttributes, cleanedProps } = useStyles('Button', { - size: 'small', - variant: 'primary', - ...props, - }); + const { classNames, dataAttributes, cleanedProps } = useStyles( + ButtonDefinition, + { + size: 'small', + variant: 'primary', + ...props, + }, + ); - const { classNames: classNamesButtonLink } = useStyles('ButtonLink'); + const { classNames: classNamesButtonLink } = + useStyles(ButtonLinkDefinition); const { children, className, iconStart, iconEnd, href, ...rest } = cleanedProps; const isExternal = isExternalLink(href); - // If it's an external link, render RALink without RouterProvider - if (isExternal) { - return ( - + {iconStart} {children} {iconEnd} - - ); + + + ); + + // If it's an external link, render RALink without RouterProvider + if (isExternal) { + return linkButton; } // For internal links, use RouterProvider return ( - - {iconStart} - {children} - {iconEnd} - + {linkButton} ); }, diff --git a/packages/ui/src/components/ButtonLink/definition.ts b/packages/ui/src/components/ButtonLink/definition.ts new file mode 100644 index 0000000000..daf3fae1ea --- /dev/null +++ b/packages/ui/src/components/ButtonLink/definition.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for ButtonLink + * @public + */ +export const ButtonLinkDefinition = { + classNames: { + root: 'bui-ButtonLink', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/ButtonLink/index.ts b/packages/ui/src/components/ButtonLink/index.ts index bbfc696c67..b98f84d489 100644 --- a/packages/ui/src/components/ButtonLink/index.ts +++ b/packages/ui/src/components/ButtonLink/index.ts @@ -15,4 +15,5 @@ */ export * from './ButtonLink'; +export { ButtonLinkDefinition } from './definition'; export * from './types'; diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx index b942b70649..a3f4995588 100644 --- a/packages/ui/src/components/Card/Card.tsx +++ b/packages/ui/src/components/Card/Card.tsx @@ -17,6 +17,7 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; import { useStyles } from '../../hooks/useStyles'; +import { CardDefinition } from './definition'; import type { CardProps, CardHeaderProps, @@ -31,7 +32,7 @@ import styles from './Card.module.css'; * @public */ export const Card = forwardRef((props, ref) => { - const { classNames, cleanedProps } = useStyles('Card', props); + const { classNames, cleanedProps } = useStyles(CardDefinition, props); const { className, ...rest } = cleanedProps; return ( @@ -50,7 +51,7 @@ export const Card = forwardRef((props, ref) => { */ export const CardHeader = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Card', props); + const { classNames, cleanedProps } = useStyles(CardDefinition, props); const { className, ...rest } = cleanedProps; return ( @@ -74,7 +75,7 @@ export const CardHeader = forwardRef( */ export const CardBody = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Card', props); + const { classNames, cleanedProps } = useStyles(CardDefinition, props); const { className, ...rest } = cleanedProps; return ( @@ -94,7 +95,7 @@ export const CardBody = forwardRef( */ export const CardFooter = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Card', props); + const { classNames, cleanedProps } = useStyles(CardDefinition, props); const { className, ...rest } = cleanedProps; return ( diff --git a/packages/ui/src/components/Card/definition.ts b/packages/ui/src/components/Card/definition.ts new file mode 100644 index 0000000000..cd0c5f634b --- /dev/null +++ b/packages/ui/src/components/Card/definition.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Card + * @public + */ +export const CardDefinition = { + classNames: { + root: 'bui-Card', + header: 'bui-CardHeader', + body: 'bui-CardBody', + footer: 'bui-CardFooter', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Card/index.ts b/packages/ui/src/components/Card/index.ts index 4b710ee7f1..dc7dbd4ead 100644 --- a/packages/ui/src/components/Card/index.ts +++ b/packages/ui/src/components/Card/index.ts @@ -15,6 +15,7 @@ */ export { Card, CardHeader, CardBody, CardFooter } from './Card'; +export { CardDefinition } from './definition'; export type { CardProps, diff --git a/packages/ui/src/components/Checkbox/Checkbox.module.css b/packages/ui/src/components/Checkbox/Checkbox.module.css index 2e08b08c1a..23a88d2b80 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.module.css +++ b/packages/ui/src/components/Checkbox/Checkbox.module.css @@ -17,35 +17,7 @@ @layer tokens, base, components, utilities; @layer components { - .bui-CheckboxRoot { - border: none; - display: flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - box-shadow: inset 0 0 0 1px var(--bui-border); - cursor: pointer; - border-radius: 2px; - transition: background-color 0.2s ease-in-out; - background-color: var(--bui-bg-surface-1); - padding: 0; - flex-shrink: 0; - } - - .bui-CheckboxRoot:focus-visible { - transition: none; - outline: 2px solid var(--bui-ring); - outline-offset: 2px; - } - - .bui-CheckboxRoot[data-checked] { - background-color: var(--bui-bg-solid); - box-shadow: none; - color: var(--bui-fg-solid); - } - - .bui-CheckboxLabel { + .bui-Checkbox { display: flex; flex-direction: row; align-items: center; @@ -55,18 +27,49 @@ font-weight: var(--bui-font-weight-regular); color: var(--bui-fg-primary); user-select: none; + cursor: pointer; + } - &:hover { - & .bui-CheckboxRoot:not([data-checked]) { - box-shadow: inset 0 0 0 1px var(--bui-border-hover); - } - } + .bui-Checkbox[data-disabled] { + cursor: not-allowed; + opacity: 0.5; } .bui-CheckboxIndicator { + border: none; display: flex; align-items: center; justify-content: center; - color: var(--bui-fg-solid); + width: 1rem; + height: 1rem; + box-shadow: inset 0 0 0 1px var(--bui-border); + border-radius: 2px; + transition: background-color 0.2s ease-in-out; + background-color: var(--bui-bg-surface-1); + padding: 0; + flex-shrink: 0; + color: transparent; + + .bui-Checkbox[data-focus-visible] & { + transition: none; + outline: 2px solid var(--bui-ring); + outline-offset: 2px; + } + + .bui-Checkbox[data-selected] & { + background-color: var(--bui-bg-solid); + box-shadow: none; + color: var(--bui-fg-solid); + } + + .bui-Checkbox[data-hovered]:not([data-selected]) & { + box-shadow: inset 0 0 0 1px var(--bui-border-hover); + } + + @media (prefers-reduced-motion: reduce) { + & { + transition: none; + } + } } } diff --git a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx index 080cae40b6..a41b7f2c8d 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx @@ -17,7 +17,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { Checkbox } from './Checkbox'; import { Flex } from '../Flex'; -import { Text } from '../Text'; const meta = { title: 'Backstage UI/Checkbox', @@ -29,31 +28,20 @@ type Story = StoryObj; export const Default: Story = { args: { - label: 'Accept terms and conditions', + children: 'Accept terms and conditions', }, }; export const AllVariants: Story = { + ...Default, render: () => ( - - - - - - - ), -}; - -export const Playground: Story = { - render: () => ( - - All variants - - - - - - + + Unchecked + Checked + Disabled + + Checked & Disabled + ), }; diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx index 2414f29f5f..97a603daf2 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -15,41 +15,33 @@ */ import { forwardRef } from 'react'; -import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox'; +import { Checkbox as RACheckbox } from 'react-aria-components'; import type { CheckboxProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { CheckboxDefinition } from './definition'; import clsx from 'clsx'; import styles from './Checkbox.module.css'; import { RiCheckLine } from '@remixicon/react'; /** @public */ -export const Checkbox = forwardRef( +export const Checkbox = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Checkbox', props); - const { label, onChange, className, ...rest } = cleanedProps; + const { classNames } = useStyles(CheckboxDefinition); + const { className, children, ...rest } = props; - const checkboxElement = ( - - - - - ); - - return label ? ( - - ) : ( - checkboxElement +
    + {children} + ); }, ); diff --git a/packages/ui/src/components/Checkbox/definition.ts b/packages/ui/src/components/Checkbox/definition.ts new file mode 100644 index 0000000000..022f2a0af1 --- /dev/null +++ b/packages/ui/src/components/Checkbox/definition.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Checkbox + * @public + */ +export const CheckboxDefinition = { + classNames: { + root: 'bui-Checkbox', + indicator: 'bui-CheckboxIndicator', + }, + dataAttributes: { + selected: [true, false] as const, + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Checkbox/index.ts b/packages/ui/src/components/Checkbox/index.ts index f6dfbf6beb..7c0cb5b414 100644 --- a/packages/ui/src/components/Checkbox/index.ts +++ b/packages/ui/src/components/Checkbox/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ export { Checkbox } from './Checkbox'; +export { CheckboxDefinition } from './definition'; export type { CheckboxProps } from './types'; diff --git a/packages/ui/src/components/Checkbox/types.ts b/packages/ui/src/components/Checkbox/types.ts index 0240954afa..86e4b620b8 100644 --- a/packages/ui/src/components/Checkbox/types.ts +++ b/packages/ui/src/components/Checkbox/types.ts @@ -13,17 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { CheckboxProps as RACheckboxProps } from 'react-aria-components'; /** @public */ -export interface CheckboxProps { - label?: string; - defaultChecked?: boolean; - checked?: boolean; - onChange?: (checked: boolean) => void; - disabled?: boolean; - required?: boolean; - className?: string; - name?: string; - value?: string; - style?: React.CSSProperties; +export interface CheckboxProps extends RACheckboxProps { + children: React.ReactNode; } diff --git a/packages/ui/src/components/Collapsible/Collapsible.stories.tsx b/packages/ui/src/components/Collapsible/Collapsible.stories.tsx deleted file mode 100644 index 47b76f2f56..0000000000 --- a/packages/ui/src/components/Collapsible/Collapsible.stories.tsx +++ /dev/null @@ -1,84 +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 type { Meta, StoryObj } from '@storybook/react-vite'; -import { Collapsible } from './Collapsible'; -import { Button } from '../Button'; -import { Box } from '../Box'; -import { Text } from '../Text'; -import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'; - -const meta = { - title: 'Backstage UI/Collapsible', - component: Collapsible.Root, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - style: { - display: 'flex', - flexDirection: 'column', - gap: 'var(--bui-space-2)', - alignItems: 'center', - }, - children: ( - <> - ( - - )} - /> - - - - It's the edge of the world and all of Western civilization - - - The sun may rise in the East, at least it settled in a final - location - - It's understood that Hollywood sells Californication - - - - ), - }, -}; - -export const Open: Story = { - args: { - ...Default.args, - defaultOpen: true, - }, -}; diff --git a/packages/ui/src/components/Collapsible/Collapsible.tsx b/packages/ui/src/components/Collapsible/Collapsible.tsx deleted file mode 100644 index c9417d2019..0000000000 --- a/packages/ui/src/components/Collapsible/Collapsible.tsx +++ /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 { forwardRef } from 'react'; -import { Collapsible as CollapsiblePrimitive } from '@base-ui-components/react/collapsible'; -import clsx from 'clsx'; -import { useStyles } from '../../hooks/useStyles'; -import styles from './Collapsible.module.css'; - -const CollapsibleRoot = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles('Collapsible', props); - - return ( - - ); -}); -CollapsibleRoot.displayName = CollapsiblePrimitive.Root.displayName; - -const CollapsibleTrigger = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles('Collapsible', props); - - return ( - - ); -}); -CollapsibleTrigger.displayName = CollapsiblePrimitive.Trigger.displayName; - -const CollapsiblePanel = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - const { classNames, cleanedProps } = useStyles('Collapsible', props); - - return ( - - ); -}); -CollapsiblePanel.displayName = CollapsiblePrimitive.Panel.displayName; - -/** - * Collapsible is a component that allows you to collapse and expand content. - * It is a wrapper around the CollapsiblePrimitive component from base-ui-components. - * - * @public - */ -export const Collapsible = { - Root: CollapsibleRoot, - Trigger: CollapsibleTrigger, - Panel: CollapsiblePanel, -}; diff --git a/packages/ui/src/components/Container/Container.tsx b/packages/ui/src/components/Container/Container.tsx index 9b15cd162e..7e3627f0b9 100644 --- a/packages/ui/src/components/Container/Container.tsx +++ b/packages/ui/src/components/Container/Container.tsx @@ -18,13 +18,14 @@ import { forwardRef } from 'react'; import { ContainerProps } from './types'; import clsx from 'clsx'; import { useStyles } from '../../hooks/useStyles'; +import { ContainerDefinition } from './definition'; import styles from './Container.module.css'; /** @public */ export const Container = forwardRef( (props, ref) => { const { classNames, utilityClasses, style, cleanedProps } = useStyles( - 'Container', + ContainerDefinition, props, ); diff --git a/packages/ui/src/components/Container/definition.ts b/packages/ui/src/components/Container/definition.ts new file mode 100644 index 0000000000..330e6adf2f --- /dev/null +++ b/packages/ui/src/components/Container/definition.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Container + * @public + */ +export const ContainerDefinition = { + classNames: { + root: 'bui-Container', + }, + utilityProps: ['my', 'mt', 'mb', 'py', 'pt', 'pb', 'display'], +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Container/index.tsx b/packages/ui/src/components/Container/index.tsx index 81614c352d..73d1a0ab6e 100644 --- a/packages/ui/src/components/Container/index.tsx +++ b/packages/ui/src/components/Container/index.tsx @@ -14,4 +14,5 @@ * limitations under the License. */ export { Container } from './Container'; +export { ContainerDefinition } from './definition'; export type { ContainerProps } from './types'; diff --git a/packages/ui/src/components/Dialog/Dialog.module.css b/packages/ui/src/components/Dialog/Dialog.module.css index 22d96146ea..5101cbf67c 100644 --- a/packages/ui/src/components/Dialog/Dialog.module.css +++ b/packages/ui/src/components/Dialog/Dialog.module.css @@ -23,7 +23,7 @@ left: 0; width: 100%; height: 100%; - background: rgba(232, 232, 232, 0.8); + background: color-mix(in srgb, var(--bui-gray-2) 80%, transparent); display: flex; align-items: center; justify-content: center; diff --git a/packages/ui/src/components/Dialog/Dialog.tsx b/packages/ui/src/components/Dialog/Dialog.tsx index 5dcd421b04..1c33588306 100644 --- a/packages/ui/src/components/Dialog/Dialog.tsx +++ b/packages/ui/src/components/Dialog/Dialog.tsx @@ -31,6 +31,7 @@ import type { import { RiCloseLine } from '@remixicon/react'; import { Button } from '../Button'; import { useStyles } from '../../hooks/useStyles'; +import { DialogDefinition } from './definition'; import { Flex } from '../Flex'; import styles from './Dialog.module.css'; @@ -42,7 +43,7 @@ export const DialogTrigger = (props: DialogTriggerProps) => { /** @public */ export const Dialog = forwardRef, DialogProps>( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Dialog', props); + const { classNames, cleanedProps } = useStyles(DialogDefinition, props); const { className, children, width, height, style, ...rest } = cleanedProps; return ( @@ -84,7 +85,7 @@ export const DialogHeader = forwardRef< React.ElementRef<'div'>, DialogHeaderProps >((props, ref) => { - const { classNames, cleanedProps } = useStyles('Dialog', props); + const { classNames, cleanedProps } = useStyles(DialogDefinition, props); const { className, children, ...rest } = cleanedProps; return ( @@ -110,7 +111,7 @@ DialogHeader.displayName = 'DialogHeader'; /** @public */ export const DialogBody = forwardRef, DialogBodyProps>( (props, ref) => { - const { classNames, cleanedProps } = useStyles('Dialog', props); + const { classNames, cleanedProps } = useStyles(DialogDefinition, props); const { className, children, ...rest } = cleanedProps; return ( @@ -132,7 +133,7 @@ export const DialogFooter = forwardRef< React.ElementRef<'div'>, React.ComponentPropsWithoutRef<'div'> >((props, ref) => { - const { classNames, cleanedProps } = useStyles('Dialog', props); + const { classNames, cleanedProps } = useStyles(DialogDefinition, props); const { className, children, ...rest } = cleanedProps; return ( diff --git a/packages/ui/src/components/Dialog/definition.ts b/packages/ui/src/components/Dialog/definition.ts new file mode 100644 index 0000000000..d2fcb7bb4d --- /dev/null +++ b/packages/ui/src/components/Dialog/definition.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Dialog + * @public + */ +export const DialogDefinition = { + classNames: { + overlay: 'bui-DialogOverlay', + dialog: 'bui-Dialog', + header: 'bui-DialogHeader', + headerTitle: 'bui-DialogHeaderTitle', + body: 'bui-DialogBody', + footer: 'bui-DialogFooter', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Dialog/index.ts b/packages/ui/src/components/Dialog/index.ts index 8db1b29ff8..57dc8aae4b 100644 --- a/packages/ui/src/components/Dialog/index.ts +++ b/packages/ui/src/components/Dialog/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ export * from './Dialog'; +export { DialogDefinition } from './definition'; export * from './types'; diff --git a/packages/ui/src/components/FieldError/FieldError.tsx b/packages/ui/src/components/FieldError/FieldError.tsx index 4c4bfb9455..0016486fe7 100644 --- a/packages/ui/src/components/FieldError/FieldError.tsx +++ b/packages/ui/src/components/FieldError/FieldError.tsx @@ -22,11 +22,12 @@ import { import clsx from 'clsx'; import styles from './FieldError.module.css'; import { useStyles } from '../../hooks/useStyles'; +import { FieldErrorDefinition } from './definition'; /** @public */ export const FieldError = forwardRef( (props: FieldErrorProps, ref) => { - const { classNames, cleanedProps } = useStyles('FieldError', props); + const { classNames, cleanedProps } = useStyles(FieldErrorDefinition, props); const { className, ...rest } = cleanedProps; return ( diff --git a/packages/ui/src/components/FieldError/definition.ts b/packages/ui/src/components/FieldError/definition.ts new file mode 100644 index 0000000000..d7174dfc99 --- /dev/null +++ b/packages/ui/src/components/FieldError/definition.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for FieldError + * @public + */ +export const FieldErrorDefinition = { + classNames: { + root: 'bui-FieldError', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/FieldError/index.ts b/packages/ui/src/components/FieldError/index.ts index 5b61f83ace..1d3abf8398 100644 --- a/packages/ui/src/components/FieldError/index.ts +++ b/packages/ui/src/components/FieldError/index.ts @@ -15,3 +15,4 @@ */ export * from './FieldError'; +export { FieldErrorDefinition } from './definition'; diff --git a/packages/ui/src/components/FieldLabel/FieldLabel.tsx b/packages/ui/src/components/FieldLabel/FieldLabel.tsx index 475b053344..3c685f9479 100644 --- a/packages/ui/src/components/FieldLabel/FieldLabel.tsx +++ b/packages/ui/src/components/FieldLabel/FieldLabel.tsx @@ -17,21 +17,29 @@ import { Label } from 'react-aria-components'; import { forwardRef } from 'react'; import type { FieldLabelProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { FieldLabelDefinition } from './definition'; import styles from './FieldLabel.module.css'; import clsx from 'clsx'; /** @public */ export const FieldLabel = forwardRef( (props: FieldLabelProps, ref) => { - const { classNames, cleanedProps } = useStyles('FieldLabel', props); - const { label, secondaryLabel, description, htmlFor, id, ...rest } = - cleanedProps; + const { classNames, cleanedProps } = useStyles(FieldLabelDefinition, props); + const { + className, + label, + secondaryLabel, + description, + htmlFor, + id, + ...rest + } = cleanedProps; if (!label) return null; return (
    diff --git a/packages/ui/src/components/FieldLabel/definition.ts b/packages/ui/src/components/FieldLabel/definition.ts new file mode 100644 index 0000000000..d6833a9478 --- /dev/null +++ b/packages/ui/src/components/FieldLabel/definition.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for FieldLabel + * @public + */ +export const FieldLabelDefinition = { + classNames: { + root: 'bui-FieldLabelWrapper', + label: 'bui-FieldLabel', + secondaryLabel: 'bui-FieldSecondaryLabel', + description: 'bui-FieldDescription', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/FieldLabel/index.ts b/packages/ui/src/components/FieldLabel/index.ts index b9cebb1bf7..0b4a8adf5f 100644 --- a/packages/ui/src/components/FieldLabel/index.ts +++ b/packages/ui/src/components/FieldLabel/index.ts @@ -15,4 +15,5 @@ */ export * from './FieldLabel'; +export { FieldLabelDefinition } from './definition'; export * from './types'; diff --git a/packages/ui/src/components/FieldLabel/types.ts b/packages/ui/src/components/FieldLabel/types.ts index dc4e444133..7f1d8a24b1 100644 --- a/packages/ui/src/components/FieldLabel/types.ts +++ b/packages/ui/src/components/FieldLabel/types.ts @@ -15,7 +15,8 @@ */ /** @public */ -export interface FieldLabelProps { +export interface FieldLabelProps + extends Pick, 'className'> { /** * The label of the text field */ diff --git a/packages/ui/src/components/Flex/Flex.tsx b/packages/ui/src/components/Flex/Flex.tsx index 27564539bd..878bcfe0ea 100644 --- a/packages/ui/src/components/Flex/Flex.tsx +++ b/packages/ui/src/components/Flex/Flex.tsx @@ -18,12 +18,13 @@ import { forwardRef } from 'react'; import { FlexProps } from './types'; import clsx from 'clsx'; import { useStyles } from '../../hooks/useStyles'; +import { FlexDefinition } from './definition'; import styles from './Flex.module.css'; /** @public */ export const Flex = forwardRef((props, ref) => { const { classNames, utilityClasses, style, cleanedProps } = useStyles( - 'Flex', + FlexDefinition, { gap: '4', ...props }, ); diff --git a/packages/ui/src/components/Flex/definition.ts b/packages/ui/src/components/Flex/definition.ts new file mode 100644 index 0000000000..d6902e2f47 --- /dev/null +++ b/packages/ui/src/components/Flex/definition.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Flex + * @public + */ +export const FlexDefinition = { + classNames: { + root: 'bui-Flex', + }, + utilityProps: [ + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + 'gap', + 'align', + 'justify', + 'direction', + ], +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Flex/index.ts b/packages/ui/src/components/Flex/index.ts index 4e4c883119..c9c5890ad9 100644 --- a/packages/ui/src/components/Flex/index.ts +++ b/packages/ui/src/components/Flex/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ export { Flex } from './Flex'; +export { FlexDefinition } from './definition'; export type { FlexProps } from './types'; diff --git a/packages/ui/src/components/Grid/Grid.tsx b/packages/ui/src/components/Grid/Grid.tsx index 61cd2fb7c7..aa30d25fc0 100644 --- a/packages/ui/src/components/Grid/Grid.tsx +++ b/packages/ui/src/components/Grid/Grid.tsx @@ -18,11 +18,12 @@ import { forwardRef } from 'react'; import clsx from 'clsx'; import type { GridItemProps, GridProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { GridDefinition, GridItemDefinition } from './definition'; import styles from './Grid.module.css'; const GridRoot = forwardRef((props, ref) => { const { classNames, utilityClasses, style, cleanedProps } = useStyles( - 'Grid', + GridDefinition, { columns: 'auto', gap: '4', ...props }, ); @@ -45,7 +46,7 @@ const GridRoot = forwardRef((props, ref) => { const GridItem = forwardRef((props, ref) => { const { classNames, utilityClasses, style, cleanedProps } = useStyles( - 'GridItem', + GridItemDefinition, props, ); diff --git a/packages/ui/src/components/Grid/definition.ts b/packages/ui/src/components/Grid/definition.ts new file mode 100644 index 0000000000..201ef4dc66 --- /dev/null +++ b/packages/ui/src/components/Grid/definition.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Grid + * @public + */ +export const GridDefinition = { + classNames: { + root: 'bui-Grid', + }, + utilityProps: [ + 'columns', + 'gap', + 'm', + 'mb', + 'ml', + 'mr', + 'mt', + 'mx', + 'my', + 'p', + 'pb', + 'pl', + 'pr', + 'pt', + 'px', + 'py', + ], +} as const satisfies ComponentDefinition; + +/** + * Component definition for GridItem + * @public + */ +export const GridItemDefinition = { + classNames: { + root: 'bui-GridItem', + }, + utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'], +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Grid/index.ts b/packages/ui/src/components/Grid/index.ts index 870ab55856..aa95e3370e 100644 --- a/packages/ui/src/components/Grid/index.ts +++ b/packages/ui/src/components/Grid/index.ts @@ -15,4 +15,5 @@ */ export { Grid } from './Grid'; +export { GridDefinition, GridItemDefinition } from './definition'; export type { GridProps, GridItemProps } from './types'; diff --git a/packages/ui/src/components/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx index ad5c704a3a..56ec200e29 100644 --- a/packages/ui/src/components/Header/Header.tsx +++ b/packages/ui/src/components/Header/Header.tsx @@ -18,6 +18,7 @@ import type { HeaderProps } from './types'; import { HeaderToolbar } from './HeaderToolbar'; import { Tabs, TabList, Tab } from '../Tabs'; import { useStyles } from '../../hooks/useStyles'; +import { HeaderDefinition } from './definition'; import { type NavigateOptions } from 'react-router-dom'; import styles from './Header.module.css'; import clsx from 'clsx'; @@ -34,9 +35,16 @@ declare module 'react-aria-components' { * @public */ export const Header = (props: HeaderProps) => { - const { classNames, cleanedProps } = useStyles('Header', props); - const { tabs, icon, title, titleLink, customActions, onTabSelectionChange } = - cleanedProps; + const { classNames, cleanedProps } = useStyles(HeaderDefinition, props); + const { + className, + tabs, + icon, + title, + titleLink, + customActions, + onTabSelectionChange, + } = cleanedProps; const hasTabs = tabs && tabs.length > 0; @@ -54,6 +62,7 @@ export const Header = (props: HeaderProps) => { className={clsx( classNames.tabsWrapper, styles[classNames.tabsWrapper], + className, )} > diff --git a/packages/ui/src/components/Header/HeaderToolbar.tsx b/packages/ui/src/components/Header/HeaderToolbar.tsx index 343922ff02..6ba03ab3a6 100644 --- a/packages/ui/src/components/Header/HeaderToolbar.tsx +++ b/packages/ui/src/components/Header/HeaderToolbar.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import { Link, RouterProvider } from 'react-aria-components'; +import { Link } from 'react-aria-components'; import { useStyles } from '../../hooks/useStyles'; +import { HeaderDefinition } from './definition'; import { useRef } from 'react'; import { RiShapesLine } from '@remixicon/react'; import type { HeaderToolbarProps } from './types'; import { Text } from '../Text'; -import { useNavigate, useHref } from 'react-router-dom'; import styles from './Header.module.css'; import clsx from 'clsx'; @@ -30,9 +30,9 @@ import clsx from 'clsx'; * @internal */ export const HeaderToolbar = (props: HeaderToolbarProps) => { - const { classNames, cleanedProps } = useStyles('Header', props); - const { icon, title, titleLink, customActions, hasTabs } = cleanedProps; - let navigate = useNavigate(); + const { classNames, cleanedProps } = useStyles(HeaderDefinition, props); + const { className, icon, title, titleLink, customActions, hasTabs } = + cleanedProps; // Refs for collision detection const toolbarWrapperRef = useRef(null); @@ -51,59 +51,61 @@ export const HeaderToolbar = (props: HeaderToolbarProps) => { ); return ( - +
    -
    + {titleLink ? ( + + {titleContent} + + ) : ( +
    + {titleContent} +
    )} - ref={toolbarContentRef} - > - - {titleLink ? ( - - {titleContent} - - ) : ( -
    - {titleContent} -
    - )} -
    -
    -
    - {customActions} -
    + +
    +
    + {customActions}
    - +
    ); }; diff --git a/packages/ui/src/components/Header/definition.ts b/packages/ui/src/components/Header/definition.ts new file mode 100644 index 0000000000..5969d7d8fa --- /dev/null +++ b/packages/ui/src/components/Header/definition.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Header + * @public + */ +export const HeaderDefinition = { + classNames: { + toolbar: 'bui-HeaderToolbar', + toolbarWrapper: 'bui-HeaderToolbarWrapper', + toolbarContent: 'bui-HeaderToolbarContent', + toolbarControls: 'bui-HeaderToolbarControls', + toolbarIcon: 'bui-HeaderToolbarIcon', + toolbarName: 'bui-HeaderToolbarName', + tabsWrapper: 'bui-HeaderTabsWrapper', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Header/index.tsx b/packages/ui/src/components/Header/index.tsx index b0ae2821b2..60685cfaa6 100644 --- a/packages/ui/src/components/Header/index.tsx +++ b/packages/ui/src/components/Header/index.tsx @@ -15,4 +15,5 @@ */ export { Header } from './Header'; +export { HeaderDefinition } from './definition'; export type { HeaderProps, HeaderTab } from './types'; diff --git a/packages/ui/src/components/Header/types.ts b/packages/ui/src/components/Header/types.ts index c7c602abfc..57dd1088f5 100644 --- a/packages/ui/src/components/Header/types.ts +++ b/packages/ui/src/components/Header/types.ts @@ -29,6 +29,7 @@ export interface HeaderProps { customActions?: React.ReactNode; tabs?: HeaderTab[]; onTabSelectionChange?: TabsProps['onSelectionChange']; + className?: string; } /** @@ -59,4 +60,5 @@ export interface HeaderToolbarProps { titleLink?: HeaderProps['titleLink']; customActions?: HeaderProps['customActions']; hasTabs?: boolean; + className?: string; } diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.tsx b/packages/ui/src/components/HeaderPage/HeaderPage.tsx index 271f011a8f..efa7ea8d58 100644 --- a/packages/ui/src/components/HeaderPage/HeaderPage.tsx +++ b/packages/ui/src/components/HeaderPage/HeaderPage.tsx @@ -19,6 +19,7 @@ import { Text } from '../Text'; import { RiArrowRightSLine } from '@remixicon/react'; import { Tabs, TabList, Tab } from '../Tabs'; import { useStyles } from '../../hooks/useStyles'; +import { HeaderPageDefinition } from './definition'; import { Container } from '../Container'; import { Link } from '../Link'; import { Fragment } from 'react/jsx-runtime'; @@ -31,11 +32,13 @@ import clsx from 'clsx'; * @public */ export const HeaderPage = (props: HeaderPageProps) => { - const { classNames, cleanedProps } = useStyles('HeaderPage', props); - const { title, tabs, customActions, breadcrumbs } = cleanedProps; + const { classNames, cleanedProps } = useStyles(HeaderPageDefinition, props); + const { className, title, tabs, customActions, breadcrumbs } = cleanedProps; return ( - +
    ((props, ref) => { const navigate = useNavigate(); - const { classNames: classNamesLink } = useStyles('Link', props); + const { classNames, dataAttributes, cleanedProps } = useStyles( + LinkDefinition, + { + variant: 'body', + weight: 'regular', + color: 'primary', + ...props, + }, + ); + const { - classNames: classNamesText, - dataAttributes: textDataAttributes, - cleanedProps, - } = useStyles('Text', { - variant: 'body', - weight: 'regular', - color: 'primary', - ...props, - }); - const { className, variant, weight, color, truncate, href, ...restProps } = - cleanedProps; + className, + href, + title, + children, + onPress, + variant, + weight, + color, + truncate, + slot, + ...restProps + } = cleanedProps; const isExternal = isExternalLink(href); + const internalRef = useRef(null); + const linkRef = (ref || internalRef) as React.RefObject; - // If it's an external link, render AriaLink without RouterProvider + // Use useLink hook to get link props + const { linkProps } = useLink( + { + href, + onPress, + ...restProps, + }, + linkRef, + ); + + const anchorElement = ( + )} + ref={linkRef} + href={href} + title={title} + className={clsx(classNames.root, styles[classNames.root], className)} + > + {children} + + ); + + // If it's an external link, render without RouterProvider if (isExternal) { - return ( - - ); + return anchorElement; } // For internal links, use RouterProvider return ( - + {anchorElement} ); }); diff --git a/packages/ui/src/components/Link/definition.ts b/packages/ui/src/components/Link/definition.ts new file mode 100644 index 0000000000..cece929f11 --- /dev/null +++ b/packages/ui/src/components/Link/definition.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Link + * @public + */ +export const LinkDefinition = { + classNames: { + root: 'bui-Link', + }, + dataAttributes: { + variant: ['subtitle', 'body', 'caption', 'label'] as const, + weight: ['regular', 'bold'] as const, + color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const, + truncate: [true, false] as const, + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Link/index.ts b/packages/ui/src/components/Link/index.ts index 1b38e89d62..7c17668029 100644 --- a/packages/ui/src/components/Link/index.ts +++ b/packages/ui/src/components/Link/index.ts @@ -15,4 +15,5 @@ */ export { Link } from './Link'; +export { LinkDefinition } from './definition'; export type { LinkProps } from './types'; diff --git a/packages/ui/src/components/Link/types.ts b/packages/ui/src/components/Link/types.ts index 33c33b26c0..796676e593 100644 --- a/packages/ui/src/components/Link/types.ts +++ b/packages/ui/src/components/Link/types.ts @@ -22,6 +22,7 @@ import type { TextWeights, } from '../../types'; import type { LinkProps as AriaLinkProps } from 'react-aria-components'; +import type { ReactNode } from 'react'; /** @public */ export interface LinkProps extends AriaLinkProps { @@ -32,4 +33,10 @@ export interface LinkProps extends AriaLinkProps { | TextColorStatus | Partial>; truncate?: boolean; + + // This is used to set the title attribute on the link + title?: string; + + // This is used to set the children of the link + children?: ReactNode; } diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index f97f3f57ff..606714583e 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -188,48 +188,56 @@ } .bui-MenuSearchField { - position: relative; font-family: var(--bui-font-regular); width: 100%; flex-shrink: 0; + display: flex; + align-items: center; + border-bottom: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + height: 2rem; &[data-empty] { .bui-MenuSearchFieldClear { - display: none; + visibility: hidden; } } } .bui-MenuSearchFieldInput { + flex: 1; display: flex; align-items: center; - padding: 0 var(--bui-space-3); + padding: 0; border: none; - border-bottom: 1px solid var(--bui-border); - background-color: var(--bui-bg-surface-1); + background-color: transparent; font-size: var(--bui-font-size-3); font-family: var(--bui-font-regular); font-weight: var(--bui-font-weight-regular); color: var(--bui-fg-primary); width: 100%; - height: 2rem; - cursor: inherit; + height: 100%; outline: none; + cursor: inherit; &::-webkit-search-cancel-button, &::-webkit-search-decoration { -webkit-appearance: none; } + + &::placeholder { + color: var(--bui-fg-secondary); + } + + &:first-child { + padding-inline: var(--bui-space-3) 0; + } } .bui-MenuSearchFieldClear { - position: absolute; - right: var(--bui-space-2); - top: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; + flex: 0 0 auto; + display: grid; + place-content: center; background-color: transparent; border: none; padding: 0; @@ -237,6 +245,12 @@ cursor: pointer; color: var(--bui-fg-secondary); transition: color 0.2s ease-in-out; + width: 2rem; + height: 2rem; + + &:hover { + color: var(--bui-fg-primary); + } & > svg { width: 1rem; diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 34eac8a128..12048776be 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -36,6 +36,7 @@ import { OverlayTriggerStateContext, } from 'react-aria-components'; import { useStyles } from '../../hooks/useStyles'; +import { MenuDefinition } from './definition'; import type { MenuTriggerProps, SubmenuTriggerProps, @@ -63,7 +64,7 @@ import clsx from 'clsx'; const rowHeight = 32; const MenuEmptyState = () => { - const { classNames } = useStyles('Menu'); + const { classNames } = useStyles(MenuDefinition); return (
    @@ -84,8 +85,9 @@ export const SubmenuTrigger = (props: SubmenuTriggerProps) => { /** @public */ export const Menu = (props: MenuProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); const { + className, placement = 'bottom start', virtualized = false, maxWidth, @@ -136,7 +138,11 @@ export const Menu = (props: MenuProps) => { return ( ) => { /** @public */ export const MenuListBox = (props: MenuListBoxProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); const { + className, selectionMode = 'single', placement = 'bottom start', virtualized = false, @@ -184,7 +191,11 @@ export const MenuListBox = (props: MenuListBoxProps) => { return ( {virtualized ? ( @@ -205,8 +216,9 @@ export const MenuListBox = (props: MenuListBoxProps) => { /** @public */ export const MenuAutocomplete = (props: MenuAutocompleteProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); const { + className, placement = 'bottom start', virtualized = false, maxWidth, @@ -229,7 +241,11 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps) => { return ( @@ -279,8 +295,9 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps) => { export const MenuAutocompleteListbox = ( props: MenuAutocompleteListBoxProps, ) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); const { + className, selectionMode = 'single', placement = 'bottom start', virtualized = false, @@ -304,7 +321,11 @@ export const MenuAutocompleteListbox = ( return ( @@ -350,8 +371,9 @@ export const MenuAutocompleteListbox = ( /** @public */ export const MenuItem = (props: MenuItemProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); const { + className, iconStart, color = 'primary', children, @@ -365,7 +387,7 @@ export const MenuItem = (props: MenuItemProps) => { if (isLink && isExternal) { return ( window.open(href, '_blank', 'noopener,noreferrer')} @@ -398,7 +420,7 @@ export const MenuItem = (props: MenuItemProps) => { return ( { /** @public */ export const MenuListBoxItem = (props: MenuListBoxItemProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); - const { children, ...rest } = cleanedProps; + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); + const { children, className, ...rest } = cleanedProps; return (
    { /** @public */ export const MenuSection = (props: MenuSectionProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); - const { children, title, ...rest } = cleanedProps; + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); + const { children, className, title, ...rest } = cleanedProps; return ( ) => { /** @public */ export const MenuSeparator = (props: MenuSeparatorProps) => { - const { classNames, cleanedProps } = useStyles('Menu', props); + const { classNames, cleanedProps } = useStyles(MenuDefinition, props); + const { className, ...rest } = cleanedProps; return ( ); }; diff --git a/packages/ui/src/components/Menu/definition.ts b/packages/ui/src/components/Menu/definition.ts new file mode 100644 index 0000000000..e4ba0d83ab --- /dev/null +++ b/packages/ui/src/components/Menu/definition.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Menu + * @public + */ +export const MenuDefinition = { + classNames: { + root: 'bui-Menu', + popover: 'bui-MenuPopover', + content: 'bui-MenuContent', + section: 'bui-MenuSection', + sectionHeader: 'bui-MenuSectionHeader', + item: 'bui-MenuItem', + itemListBox: 'bui-MenuItemListBox', + itemListBoxCheck: 'bui-MenuItemListBoxCheck', + itemWrapper: 'bui-MenuItemWrapper', + itemContent: 'bui-MenuItemContent', + itemArrow: 'bui-MenuItemArrow', + separator: 'bui-MenuSeparator', + searchField: 'bui-MenuSearchField', + searchFieldInput: 'bui-MenuSearchFieldInput', + searchFieldClear: 'bui-MenuSearchFieldClear', + emptyState: 'bui-MenuEmptyState', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Menu/index.ts b/packages/ui/src/components/Menu/index.ts index 00237712df..f42de8a7eb 100644 --- a/packages/ui/src/components/Menu/index.ts +++ b/packages/ui/src/components/Menu/index.ts @@ -26,6 +26,7 @@ export { MenuSection, MenuSeparator, } from './Menu'; +export { MenuDefinition } from './definition'; export type { MenuTriggerProps, SubmenuTriggerProps, diff --git a/packages/ui/src/components/PasswordField/PasswordField.module.css b/packages/ui/src/components/PasswordField/PasswordField.module.css index bf78ab477f..adad950816 100644 --- a/packages/ui/src/components/PasswordField/PasswordField.module.css +++ b/packages/ui/src/components/PasswordField/PasswordField.module.css @@ -23,52 +23,136 @@ font-family: var(--bui-font-regular); width: 100%; flex-shrink: 0; - /* Reserve space for browser/password manager icon (e.g. 1Password) */ - --bui-passwordmanager-icon-width: var(--bui-space-1); + + &[data-size='small'] { + --password-field-item-height: 2rem; + } + + &[data-size='medium'] { + --password-field-item-height: 2.5rem; + } } - .bui-InputVisibility { - position: absolute; - right: var(--bui-passwordmanager-icon-width); - top: 0; - bottom: 0; + .bui-PasswordFieldInputWrapper { + display: flex; + align-items: center; + border-radius: var(--bui-radius-2); + border: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; + + &[data-size='small'] { + height: 2rem; + } + + &[data-size='medium'] { + height: 2.5rem; + } + + &:focus-within { + border-color: var(--bui-border-pressed); + outline-width: 0px; + } + + &:hover { + border-color: var(--bui-border-hover); + } + + &:has([data-invalid]) { + border-color: var(--bui-fg-danger); + } + + &:has([data-disabled]) { + opacity: 0.5; + cursor: not-allowed; + border: 1px solid var(--bui-border-disabled); + } + } + + .bui-PasswordFieldIcon { + flex: 0 0 auto; + display: grid; + place-content: center; + color: var(--bui-fg-primary); + pointer-events: none; + width: var(--password-field-item-height); + height: var(--password-field-item-height); + + & svg { + .bui-PasswordField[data-size='small'] & { + width: 1rem; + height: 1rem; + } + + .bui-PasswordField[data-size='medium'] & { + width: 1.25rem; + height: 1.25rem; + } + } + } + + .bui-PasswordFieldInput { + flex: 1; + display: flex; + align-items: center; + padding: 0; + border: none; + background-color: transparent; + font-size: var(--bui-font-size-3); + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-regular); + color: var(--bui-fg-primary); + width: 100%; + height: 100%; + outline: none; + + &::-webkit-search-cancel-button, + &::-webkit-search-decoration { + -webkit-appearance: none; + } + + &::placeholder { + color: var(--bui-fg-secondary); + } + + &[data-disabled] { + cursor: not-allowed; + } + + &:first-child { + .bui-PasswordField[data-size='small'] & { + padding-inline: var(--bui-space-3) 0; + } + + .bui-PasswordField[data-size='medium'] & { + padding-inline: var(--bui-space-4) 0; + } + } + } + + .bui-PasswordFieldVisibility { + flex: 0 0 auto; + display: grid; + place-content: center; background-color: transparent; cursor: pointer; border: none; padding: 0; margin: 0; - display: flex; - align-items: center; - justify-content: center; color: var(--bui-fg-primary); + width: var(--password-field-item-height); + height: var(--password-field-item-height); - /* Size: small */ - &[data-size='small'] { - width: 2rem; - height: 2rem; - } - &[data-size='small'] svg { - width: 1rem; - height: 1rem; - } + & svg { + .bui-PasswordField[data-size='small'] & { + width: 1rem; + height: 1rem; + } - /* Size: medium */ - &[data-size='medium'] { - width: 2.5rem; - height: 2.5rem; + .bui-PasswordField[data-size='medium'] & { + width: 1.25rem; + height: 1.25rem; + } } - &[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; - } - } - - /* Ensure input has enough right padding for our toggle + PM icon */ - .bui-PasswordField .bui-InputWrapper[data-size='small'] .bui-Input { - padding-right: calc(2rem + var(--bui-passwordmanager-icon-width)); - } - - .bui-PasswordField .bui-InputWrapper[data-size='medium'] .bui-Input { - padding-right: calc(2.5rem + var(--bui-passwordmanager-icon-width)); } } diff --git a/packages/ui/src/components/PasswordField/PasswordField.tsx b/packages/ui/src/components/PasswordField/PasswordField.tsx index d7cea3a6bd..c2cfdd1081 100644 --- a/packages/ui/src/components/PasswordField/PasswordField.tsx +++ b/packages/ui/src/components/PasswordField/PasswordField.tsx @@ -26,9 +26,9 @@ import { FieldError } from '../FieldError'; import type { PasswordFieldProps } from './types'; import { useStyles } from '../../hooks/useStyles'; +import { PasswordFieldDefinition } from './definition'; import { RiEyeLine, RiEyeOffLine } from '@remixicon/react'; import stylesPasswordField from './PasswordField.module.css'; -import stylesTextField from '../TextField/TextField.module.css'; /** @public */ export const PasswordField = forwardRef( @@ -47,13 +47,11 @@ export const PasswordField = forwardRef( } }, [label, ariaLabel, ariaLabelledBy]); - const { classNames: textFieldClassNames } = useStyles('TextField'); - const { classNames: classNamesPasswordField, dataAttributes, cleanedProps, - } = useStyles('PasswordField', { + } = useStyles(PasswordFieldDefinition, { size: 'small', ...props, }); @@ -96,16 +94,16 @@ export const PasswordField = forwardRef( />
    {icon && ( )} -
    - setIsVisible(v => !v)} - className={clsx( - classNamesPasswordField.inputVisibility, - stylesPasswordField[classNamesPasswordField.inputVisibility], - )} - > - {isVisible ? : } - -
    + setIsVisible(v => !v)} + className={clsx( + classNamesPasswordField.inputVisibility, + stylesPasswordField[classNamesPasswordField.inputVisibility], + )} + > + {isVisible ? : } +
    diff --git a/packages/ui/src/components/PasswordField/definition.ts b/packages/ui/src/components/PasswordField/definition.ts new file mode 100644 index 0000000000..11272494b4 --- /dev/null +++ b/packages/ui/src/components/PasswordField/definition.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for PasswordField + * @public + */ +export const PasswordFieldDefinition = { + classNames: { + root: 'bui-PasswordField', + inputWrapper: 'bui-PasswordFieldInputWrapper', + input: 'bui-PasswordFieldInput', + inputIcon: 'bui-PasswordFieldIcon', + inputVisibility: 'bui-PasswordFieldVisibility', + }, + dataAttributes: { + size: ['small', 'medium'] as const, + }, +} as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/PasswordField/index.ts b/packages/ui/src/components/PasswordField/index.ts index df7874e1c2..705bf68c04 100644 --- a/packages/ui/src/components/PasswordField/index.ts +++ b/packages/ui/src/components/PasswordField/index.ts @@ -15,4 +15,5 @@ */ export * from './PasswordField'; +export { PasswordFieldDefinition } from './definition'; export * from './types'; diff --git a/packages/ui/src/components/PasswordField/types.ts b/packages/ui/src/components/PasswordField/types.ts index 8af9a6134c..6fade7496a 100644 --- a/packages/ui/src/components/PasswordField/types.ts +++ b/packages/ui/src/components/PasswordField/types.ts @@ -22,7 +22,7 @@ import type { FieldLabelProps } from '../FieldLabel/types'; /** @public */ export interface PasswordFieldProps extends AriaTextFieldProps, - Omit { + Omit { /** * An icon to render before the input */ diff --git a/packages/ui/src/components/Popover/Popover.module.css b/packages/ui/src/components/Popover/Popover.module.css index 873dc33e81..910c2b8c76 100644 --- a/packages/ui/src/components/Popover/Popover.module.css +++ b/packages/ui/src/components/Popover/Popover.module.css @@ -19,7 +19,8 @@ @layer components { .bui-Popover { margin-right: 12px; - overflow: scroll; + overflow-x: hidden; + overflow-y: auto; background-color: var(--bui-bg-surface-1); border: 1px solid var(--bui-border); border-radius: var(--bui-radius-3); diff --git a/packages/ui/src/components/Popover/definition.ts b/packages/ui/src/components/Popover/definition.ts new file mode 100644 index 0000000000..8b85180973 --- /dev/null +++ b/packages/ui/src/components/Popover/definition.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../../types'; + +/** + * Component definition for Popover + * @public + */ +export const PopoverDefinition = { + classNames: { + root: 'bui-Popover', + }, +} as const satisfies ComponentDefinition; diff --git a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts b/packages/ui/src/components/Popover/index.ts similarity index 83% rename from packages/frontend-plugin-api/src/apis/definitions/alpha.ts rename to packages/ui/src/components/Popover/index.ts index a4ccde1170..1e31b8657b 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts +++ b/packages/ui/src/components/Popover/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ * limitations under the License. */ -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; +export { PopoverDefinition } from './definition'; diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.module.css b/packages/ui/src/components/RadioGroup/RadioGroup.module.css index acc6d677e4..a18f2d385d 100644 --- a/packages/ui/src/components/RadioGroup/RadioGroup.module.css +++ b/packages/ui/src/components/RadioGroup/RadioGroup.module.css @@ -54,6 +54,8 @@ background: var(--bui-gray-1); border-radius: var(--bui-radius-full); transition: all 200ms; + flex-shrink: 0; + flex-grow: 0; } &[data-pressed]:before { diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.tsx b/packages/ui/src/components/RadioGroup/RadioGroup.tsx index 355e181f37..de6cc3793b 100644 --- a/packages/ui/src/components/RadioGroup/RadioGroup.tsx +++ b/packages/ui/src/components/RadioGroup/RadioGroup.tsx @@ -23,6 +23,7 @@ import clsx from 'clsx'; import { FieldLabel } from '../FieldLabel'; import { FieldError } from '../FieldError'; import { useStyles } from '../../hooks/useStyles'; +import { RadioGroupDefinition } from './definition'; import styles from './RadioGroup.module.css'; import type { RadioGroupProps, RadioProps } from './types'; @@ -30,7 +31,7 @@ import type { RadioGroupProps, RadioProps } from './types'; /** @public */ export const RadioGroup = forwardRef( (props, ref) => { - const { classNames, cleanedProps } = useStyles('RadioGroup', props); + const { classNames, cleanedProps } = useStyles(RadioGroupDefinition, props); const { className, label, @@ -83,7 +84,7 @@ RadioGroup.displayName = 'RadioGroup'; export const Radio = forwardRef((props, ref) => { const { className, ...rest } = props; - const { classNames } = useStyles('RadioGroup'); + const { classNames } = useStyles(RadioGroupDefinition); return ( , - Omit { + Omit { children?: ReactNode; } diff --git a/packages/ui/src/components/SearchField/SearchField.module.css b/packages/ui/src/components/SearchField/SearchField.module.css index 29ec2cca31..e99c212b29 100644 --- a/packages/ui/src/components/SearchField/SearchField.module.css +++ b/packages/ui/src/components/SearchField/SearchField.module.css @@ -18,11 +18,23 @@ @layer components { .bui-SearchField { + display: flex; + flex-direction: column; + font-family: var(--bui-font-regular); + width: 100%; flex: 1; flex-shrink: 0; + &[data-size='small'] { + --search-field-item-height: 2rem; + } + + &[data-size='medium'] { + --search-field-item-height: 2.5rem; + } + &[data-empty] { - .bui-InputClear { + .bui-SearchFieldClear { display: none; } } @@ -49,20 +61,20 @@ height: 2rem; } - &[data-size='medium'] .bui-Input { + &[data-size='medium'] .bui-SearchFieldInput { &::placeholder { opacity: 0; } } - &[data-size='small'] .bui-Input { + &[data-size='small'] .bui-SearchFieldInput { &::placeholder { opacity: 0; } } - .bui-InputWrapper { - .bui-Input[data-icon] { + .bui-SearchFieldInputWrapper { + .bui-SearchFieldInput[data-icon] { padding-right: 0px; } } @@ -70,48 +82,111 @@ } } - .bui-SearchField .bui-Input { - transition: padding 0.3s ease-in-out, border-color 0.2s ease-in-out, - outline-color 0.2s ease-in-out; - - &[data-hovered] { - border-color: var(--bui-border-hover); - } - - &[data-focused] { - border-color: var(--bui-border-pressed); - outline-width: 0px; - } - } - - .bui-SearchField .bui-InputWrapper { - .bui-Input[data-icon] { - padding-right: var(--bui-space-6); - } - } - - .bui-SearchField .bui-InputIcon { - left: 0; + .bui-SearchFieldInputWrapper { display: flex; - justify-content: center; + align-items: center; + border-radius: var(--bui-radius-2); + border: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; &[data-size='small'] { - width: var(--bui-space-8); + height: 2rem; } &[data-size='medium'] { - width: var(--bui-space-10); + height: 2.5rem; + } + + &:focus-within { + border-color: var(--bui-border-pressed); + outline-width: 0px; + } + + &:hover { + border-color: var(--bui-border-hover); + } + + &[data-invalid] { + border-color: var(--bui-fg-danger); + } + + &[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + border: 1px solid var(--bui-border-disabled); } } - .bui-InputClear { - position: absolute; - right: 0; - top: 0; - bottom: 0; + .bui-SearchFieldInputIcon { + flex: 0 0 auto; + display: grid; + place-content: center; + color: var(--bui-fg-primary); + pointer-events: none; + width: var(--search-field-item-height); + height: var(--search-field-item-height); + /* To animate the icon when the input is collapsed */ + transition: opacity 0.2s ease-in-out; + + & svg { + .bui-SearchField[data-size='small'] & { + width: 1rem; + height: 1rem; + } + + .bui-SearchField[data-size='medium'] & { + width: 1.25rem; + height: 1.25rem; + } + } + } + + .bui-SearchFieldInput { + flex: 1; display: flex; align-items: center; - justify-content: center; + padding: 0; + border: none; + background-color: transparent; + font-size: var(--bui-font-size-3); + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-regular); + color: var(--bui-fg-primary); + transition: padding 0.3s ease-in-out; + width: 100%; + height: 100%; + outline: none; + cursor: inherit; + + &::-webkit-search-cancel-button, + &::-webkit-search-decoration { + -webkit-appearance: none; + } + + &::placeholder { + color: var(--bui-fg-secondary); + } + + &[data-disabled] { + cursor: not-allowed; + } + + &:first-child { + .bui-SearchField[data-size='small'] & { + padding-inline: var(--bui-space-3) 0; + } + + .bui-SearchField[data-size='medium'] & { + padding-inline: var(--bui-space-3) 0; + } + } + } + + .bui-SearchFieldClear { + flex: 0 0 auto; + display: grid; + place-content: center; background-color: transparent; border: none; padding: 0; @@ -119,24 +194,16 @@ cursor: pointer; color: var(--bui-fg-secondary); transition: color 0.2s ease-in-out; - } + width: var(--search-field-item-height); + height: var(--search-field-item-height); - .bui-InputClear:hover { - color: var(--bui-fg-primary); - } + &:hover { + color: var(--bui-fg-primary); + } - .bui-InputClear[data-size='small'] { - width: 2rem; - height: 2rem; - } - - .bui-InputClear[data-size='medium'] { - width: 2.5rem; - height: 2.5rem; - } - - .bui-InputClear svg { - width: 1rem; - height: 1rem; + & svg { + width: 1rem; + height: 1rem; + } } } diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx index 820ceb42b9..775b7accc6 100644 --- a/packages/ui/src/components/SearchField/SearchField.tsx +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -25,8 +25,8 @@ import { FieldLabel } from '../FieldLabel'; import { FieldError } from '../FieldError'; import { RiSearch2Line, RiCloseCircleLine } from '@remixicon/react'; import { useStyles } from '../../hooks/useStyles'; -import stylesSearchField from './SearchField.module.css'; -import stylesTextField from '../TextField/TextField.module.css'; +import { SearchFieldDefinition } from './definition'; +import styles from './SearchField.module.css'; import type { SearchFieldProps } from './types'; @@ -50,19 +50,15 @@ export const SearchField = forwardRef( } }, [label, ariaLabel, ariaLabelledBy]); - const { classNames: textFieldClassNames } = useStyles('TextField'); - - const { - classNames: searchFieldClassNames, - dataAttributes, - style, - cleanedProps, - } = useStyles('SearchField', { - size: 'small', - placeholder: 'Search', - startCollapsed: false, - ...props, - }); + const { classNames, dataAttributes, style, cleanedProps } = useStyles( + SearchFieldDefinition, + { + size: 'small', + placeholder: 'Search', + startCollapsed: false, + ...props, + }, + ); const { className, @@ -101,13 +97,7 @@ export const SearchField = forwardRef( return ( ( />
    {icon !== false && ( )} + - - {options?.map(option => ( - -
    - -
    - - {option.label} - -
    - ))} -
    +
    ); diff --git a/packages/ui/src/components/Select/SelectContent.tsx b/packages/ui/src/components/Select/SelectContent.tsx new file mode 100644 index 0000000000..7f63ad2a0a --- /dev/null +++ b/packages/ui/src/components/Select/SelectContent.tsx @@ -0,0 +1,75 @@ +/* + * 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 { + Input, + SearchField, + Autocomplete, + Button, +} from 'react-aria-components'; +import { useFilter } from 'react-aria'; +import { RiCloseCircleLine } from '@remixicon/react'; +import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; +import { SelectDefinition } from './definition'; +import { SelectListBox } from './SelectListBox'; +import styles from './Select.module.css'; +import type { Option } from './types'; + +interface SelectContentProps { + searchable?: boolean; + searchPlaceholder?: string; + options?: Array